Spring 之源码剖析AOP

一、概述

面向切面编程:这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。

面向切面编程的前世今生

①面向切面编程(AOP是Aspect Oriented Program的首字母缩写),面向对象的特点是继承、多态和封装。而封装就要求将功能分散到不同的对象中去,这在软件设计中往往称为职责分配。实际上也就是说,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了。这样做的好处是降低了代码的复杂程度,使类可重用。
②但是人们也发现,在分散代码的同时,也增加了代码的重复性。什么意思呢?比如说,我们在两个类中,可能都需要在每个方法中做日志。按面向对象的设计方法,我们就必须在两个类的方法中都加入日志的内容。也许他们是完全相同的,但就是因为面向对象的设计让类与类之间无法联系,而不能将这些重复的代码统一起来。
③也许有人会说,那好办啊,我们可以将这段代码写在一个独立的类独立的方法里,然后再在这两个类中调用。但是,这样一来,这两个类跟我们上面提到的独立的类就有耦合了,它的改变会影响这两个类。那么,有没有什么办法,能让我们在需要的时候,随意地加入代码呢?这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
④一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。
⑤这样看来,AOP其实只是OOP的补充而已。OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码。有了AOP,OOP变得立体了。如果加上时间维度,AOP使OOP由原来的二维变为三维了,由平面变成立体了。从技术上来说,AOP基本上是通过代理机制实现的。

二、代理

静态代理

静态代理通常用于对原有业务逻辑的扩充。比如持有二方包的某个类,并调用了其中的某些方法。然后出于某种原因,比如记录日志、打印方法执行时间,但是又不好将这些逻辑写入二方包的方法里。所以可以创建一个代理类实现和二方方法相同的方法,通过让代理类持有真实对象,然后在原代码中调用代理类方法,来达到添加我们需要业务逻辑的目的。这其实也就是代理模式的一种实现,通过对真实对象的封装,来实现扩展性。(共同接口、真实对象、代理对象)

静态代理的优点和缺点:

优点:扩展原功能,不侵入原代码。
缺点:假如有这样一个需求,有十个不同的RealObject,同时我们要去代理的方法是不同的。
方案一:创建十个不同的代理对象。
方案二:通过创建一个proxy,持有不同的realObject

动态代理

动态代理的目的就是为了解决静态代理的缺点。通过使用动态代理,我们可以通过在运行时,动态生成一个持有RealObject、并实现代理接口的Proxy,同时注入我们相同的扩展逻辑。哪怕你要代理的RealObject是不同的对象,甚至代理不同的方法,都可以动过动态代理,来扩展功能。简单理解,动态代理就是我们上面提到的方案一,只不过这些proxy的创建都是自动的并且是在运行期生成的。

动态代理基本用法:

使用动态代理,需要将要扩展的功能写在一个InvocationHandler 实现类里:

public class DynamicProxyHandler implements InvocationHandler {
      private Object realObject;
      public DynamicProxyHandler(Object realObject) {
         this.realObject = realObject;
      }
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //代理扩展逻辑
        System.out.println("proxy do");
        return method.invoke(realObject, args);
      }
}

这个Handler中的invoke方法中实现了代理类要扩展的公共功能。

public static void main(String[] args) {
        RealObject realObject = new RealObject();
        Action proxy = (Action) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Action.class}, 
            new DynamicProxyHandler(realObject));
        proxy.doSomething();
 }

Proxy.newProxyInstance传入的是一个ClassLoader,一个代理接口,和我们定义的handler,返回的是一个Proxy的实例。仔细体会这个过程,其实有点类似我们在静态代理中提到的方案一,生成了一个包含我们扩展功能,持有RealObject引用,实现Action接口的代理实例Proxy。只不过这个Proxy不是我们自己写的,而是java帮我们生成的。
让我们再回顾一下代理三要素:真实对象:RealObject,代理接口:Action,代理实例:Proxy上面的代码实含义也就是,输入 RealObject、Action,返回一个Proxy。妥妥的代理模式。综上,动态生成+代理模式,也就是动态代理。

生成动态代理对象的整体流程:

①生成代理类Proxy的Class对象。
②如果Class作用域为私有,通过 setAccessible 支持访问
③获取Proxy Class构造函数,创建Proxy代理实例。
生成Class对象的方法中,先是通过传进来的ClassLoader参数和Class[] 数组作为组成键,维护了一个对于Proxy的Class对象的缓存。这样需要相同Proxy的Class对象时,只需要创建一次。
第一次创建该Class文件时,为了线程安全,方法进行了大量的处理,最后会来到ProxyClassFactory的apply方法中,经过以下流程:
①校验传入的接口是否由传入的ClassLoader加载的。
②校验传入是否是接口的Class对象。
③校验是否传入重复的接口。
④拼装代理类包名和类名,生成.class 文件的字节码。
⑤调用native方法,传入字节码,生成Class对象。

Java动态代理的两种实现:

JDK动态代理及实现

原理:Java反射机制可以生成任意类型的动态代理类。java.lang.reflect 包下面的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。

CGLIB动态代理及实现

原理:通过字节码技术为需要代理的目标对象创建一个子类对象,并在子类对象中拦截所有父类(即需要代理的类)方法的调用,然后在方法调用前后调用后都可以加入自己想要执行的代码。但因为采用的是继承,所以不能对final修饰的类和final方法进行代理。

你可能感兴趣的:(Spring 之源码剖析AOP)