动态代理概述
什么是代理
代理模式(Proxy pattern): 为另一个对象提供一个替身或占位符以控制对这个对象的访问

什么是动态代理?
动态代理就是,在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。
在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作。
代理的创建
创建代理的方法是postProcessAfterInitialization:如果bean被子类标识为代理,则使用配置的拦截器创建一个代理
wrapIfNecessary方法主要用于判断是否需要创建代理,如果Bean能够获取到advisor才需要创建代理
获取所有的Advisor
我们看下获取所有advisor的方法getAdvicesAndAdvisorsForBean
通过findEligibleAdvisors方法获取advisor, 如果获取不到返回DO_NOT_PROXY(不需要创建代理),findEligibleAdvisors方法如下
获取所有切面类的切面方法生成Advisor
找到这些Advisor中能够应用于beanClass的Advisor
创建代理的入口方法
获取所有advisor后,如果有advisor,则说明需要增强,即需要创建代理,创建代理的方法如下:
proxyFactory.getProxy(classLoader)

依据条件创建代理(jdk或cglib)
DefaultAopProxyFactory.createAopProxy
小结
config.isOptimize() 是通过optimize设置,表示配置是自定义的,默认是false;
config.isProxyTargetClass()是通过
<aop:config proxy-target-class="true" />来配置的,表示优先使用cglib代理,默认是false;hasNoUserSuppliedProxyInterfaces(config) 表示是否目标类实现了接口
由此可以知道:
Spring默认在目标类实现接口时是通过JDK代理实现的,只有非接口的是通过Cglib代理实现的。当设置proxy-target-class为true时在目标类不是接口或者代理类时优先使用cglib代理实现。
JDK代理
JDK动态代理是有JDK提供的工具类Proxy实现的,动态代理类是在运行时生成指定接口的代理类,每个代理实例(实现需要代理的接口)都有一个关联的调用处理程序对象,此对象实现了InvocationHandler,最终的业务逻辑是在InvocationHandler实现类的invoke方法上。
JDK代理的流程如下:
JDK代理自动生成的class是由sun.misc.ProxyGenerator来生成的。
ProxyGenerator生成代码
我们看下sun.misc.ProxyGenerator生成代码的逻辑:
generateClassFile方法如下:
一共三个步骤(把大象装进冰箱分几步?):
第一步:(把冰箱门打开)准备工作,将所有方法包装成ProxyMethod对象,包括Object类中hashCode、equals、toString方法,以及被代理的接口中的方法
第二步:(把大象装进去)为代理类组装字段,构造函数,方法,static初始化块等
第三步:(把冰箱门带上)写入class文件
从生成的Proxy代码看执行流程
从上述sun.misc.ProxyGenerator类中可以看到,这个类里面有一个配置参数sun.misc.ProxyGenerator.saveGeneratedFiles,可以通过这个参数将生成的Proxy类保存在本地,比如设置为true 执行后,生成的文件如下:
我们看下生成后的代码:
上述代码是比较容易理解的,我就不画图了。
主要流程是:
ProxyGenerator创建Proxy的具体类$Proxy0
由static初始化块初始化接口方法:2个IUserService接口中的方法,3个Object中的接口方法
由构造函数注入InvocationHandler
执行的时候,通过ProxyGenerator创建的Proxy,调用InvocationHandler的invoke方法,执行我们自定义的invoke方法
SpringAOP中JDK代理的实现
SpringAOP扮演的是JDK代理的创建和调用两个角色,我们通过这两个方向来看下SpringAOP的代码(JdkDynamicAopProxy类)
SpringAOP Jdk代理的创建
代理的创建比较简单,调用getProxy方法,然后直接调用JDK中Proxy.newProxyInstance()方法将classloader和被代理的接口方法传入即可。
SpringAOP Jdk代理的执行
执行的方法如下:
CGLIB代理
代理的流程

在上图中,我们可以通过在Enhancer中配置更多的参数来控制代理的行为,比如如果只希望增强这个类中的一个方法(而不是所有方法),那就增加callbackFilter来对目标类中方法进行过滤;Enhancer可以有更多的参数类配置其行为,不过我们在学习上述主要的流程就够了。
final方法为什么不能被代理?很显然final方法没法被子类覆盖,当然不能代理了。
Mockito为什么不能mock静态方法?因为mockito也是基于cglib动态代理来实现的,static方法也不能被子类覆盖,所以显然不能mock。但PowerMock可以mock静态方法,因为它直接在bytecode上工作。
SpringAOP中Cglib代理的实现
SpringAOP封装了cglib,通过其进行动态代理的创建。
我们看下CglibAopProxy的getProxy方法
获取callback的方法如下,提几个理解的要点吧,具体读者在学习的时候建议把我的例子跑一下,然后打一个断点进行理解。
rootClass: 即目标代理类advised: 包含上文中我们获取到的advisor增强器的集合exposeProxy: 在xml配置文件中配置的,背景就是如果在事务A中使用了代理,事务A调用了目标类的的方法a,在方法a中又调用目标类的方法b,方法a,b同时都是要被增强的方法,如果不配置exposeProxy属性,方法b的增强将会失效,如果配置exposeProxy,方法b在方法a的执行中也会被增强了DynamicAdvisedInterceptor: 拦截器将advised(包含上文中我们获取到的advisor增强器)构建配置的AOP的callback(第一个callback)targetInterceptor: xml配置的optimize属性使用的(第二个callback)最后连同其它5个默认的Interceptor 返回作为cglib的拦截器链,之后通过CallbackFilter的accpet方法返回的索引从这个集合中返回对应的拦截增强器执行增强操作。
可以结合调试,方便理解

AOP在嵌套方法调用时不生效
在一个实现类中,有2个方法,方法A,方法B,其中方法B上面有个注解切面,当方法B被外部调用的时候,会进入切面方法。 但当方法B是被方法A调用时,并不能从方法B的注解上,进入到切面方法,即我们经常碰到的方法嵌套时,AOP注解不生效的问题。
案例
外部调用AOP方法正常进入
通过外部,调用方法B,可以正常进入切面方法,这个场景的代码如下:
注解类:
切面类
接口类
服务实现类
测试方法
输出结果:
方法嵌套调用,AOP不生效
上面的代码,做下修改。在DemoServiceImpl实现类中,通过方法A去调用方法B,然后在单元测试类中,调用方法A。代码修改后如下:
服务实现类:
输出结果:
原因分析
场景1中,通过外部调用方法B,是由于spring在启动时,根据切面类及注解,生成了DemoService的代理类,在调用方法B时,实际上是代理类先对目标方法进行了业务增强处理(执行切面类中的业务逻辑),然后再调用方法B本身。所以场景1可以正常进入切面方法;
场景2中,通过外部调用的是方法A,虽然spring也会创建一个cglib的代理类去调用方法A,但当方法A调用方法B的时候,属于类里面的内部调用,使用的是实例对象本身去去调用方法B,非aop的cglib代理对象调用,方法B自然就不会进入到切面方法了。
解决方案
但实际上我们期望的是,方法A在调用方法B的时候,仍然能够进入切面方法,即需要AOP切面生效。这种情况下,在调用方法B的时候,需要使用AopContext.currentProxy()获取当前的代理对象,然后使用代理对象调用方法B。
注:需要开启 exposeProxy=true 的配置,springboot项目中,可以在启动类上面,添加 @EnableAspectJAutoProxy(exposeProxy = true)注解。