Spring Aop之Cglib实现原理详解

Spring Aop实现对目标对象的代理,主要有两种方式:Jdk代理和Cglib代理。这两种代理的区别在于,Jdk代理与目标类都会实现同一个接口,并且在代理类中会调用目标类中被代理的方法,调用者实际调用的则是代理类的方法,通过这种方式我们就可以在代理类中织入切面逻辑;Jdk代理存在的问题在于目标类被代理的方法必须实现某个接口,Cglib代理则是为了解决这个问题而存在的,其实现代理的方式是通过为目标类动态生成一个子类,通过在子类中织入相应逻辑来达到织入代理逻辑的目的。

关于Jdk代理和Cglib代理,其优缺点主要在于:

Jdk代理生成的代理类只有一个,因而其编译速度是非常快的;而由于被代理的目标类是动态传入代理类中的,Jdk代理的执行效率相对来说低一点,这也是Jdk代理被称为动态代理的原因;Cglib代理需要为每个目标类生成相应的子类,因而在实际运行过程中,其可能会生成非常多的子类,过多的子类始终不是太好的,因为这影响了虚拟机编译类的效率;但由于在调用过程中,代理类的方法是已经静态编译生成了的,因而Cglib代理的执行效率相对来说高一些。

本文主要讲解Spring Aop是如何通过Cglib代理实现将切面逻辑织入目标类的。

1. AopProxy织入对象生成

前面我们讲过,Spring Aop织入切面逻辑的入口方法是AbstractAutoProxyCreator.createProxy()方法,如下是该方法的源码:

Spring Aop之Cglib实现原理详解
Spring Aop之Cglib实现原理详解

可以看到,在生成代理类之前,主要做了两件事:①判断使用Jdk代理还是Cglib代理;②设置相关的属性。这里我们继续看最后的ProxyFactory.getProxy()方法:

Spring Aop之Cglib实现原理详解

上面的createAopProxy()方法可以理解为一个工厂方法,返回值是一个AopProxy类型的对象,其内部根据具体的条件生成相应的子类对象,即JdkDynamicAopProxy和ObjenesisCglibAopProxy。后面则通过调用AopProxy.getProxy()方法获取代理过的对象。如下是createAopProxy()方法的实现逻辑:

Spring Aop之Cglib实现原理详解

这里可以看到,本文需要讲解的Cglib代理逻辑的织入就在ObjenesisCglibAopProxy.getProxy()方法中。

2. 代理逻辑的织入

关于代理逻辑的织入,其实现主体还是通过Enhancer来实现,即通过需要织入的Advisor列表,生成Callback对象,并将其设置到Enhancer对象中,最后通过Enhancer生成目标对象。如下是AopProxy.getProxy()方法的源码:

Spring Aop之Cglib实现原理详解
Spring Aop之Cglib实现原理详解
Spring Aop之Cglib实现原理详解

可以看到,这里的AopProxy.getProxy()方法就是生成代理对象的主干逻辑。上面的逻辑中主要有两个部分需要重点讲解:①如果获取Callback数组;②CallbackFilter的作用。关于第一点,我们后面会进行重点讲解,至于第二点,这里我们需要理解的就是CallbackFilter.accept()方法接收一个Method类型的参数,该参数也即当前要生成的代理逻辑的方法,这里的accept()方法将返回目标当前要织入代理逻辑的方法所需要使用的切面逻辑,也即Callback对象在Callback数组中的下标。关于CallbackFilter的使用原理,读者可以阅读实战CGLib系列之proxy篇(二):回调过滤CallbackFilter这篇文章。下面我们继续阅读getCallbacks()的源码:

Spring Aop之Cglib实现原理详解
Spring Aop之Cglib实现原理详解
Spring Aop之Cglib实现原理详解

这里的getCallbacks()方法主要做了三件事:①获取目标对象的动态调用链;②判断是否设置了exposeProxy属性,如果设置了,则生成一个可以暴露代理对象的Callback对象,否则生成一个不做任何处理直接调用目标对象的Callback对象;③判断目标对象是否是静态的,并且当前的切面逻辑是否是固定的,如果是,则将目标对象和调用链进行缓存,以便后续直接调用。这里需要说明的一个点在于第三点,因为在判断目标对象为静态对象,并且调用链是固定的时候,会将目标对象和调用链进行缓存,并且封装到指定的Callback对象中。这里读者可能会疑问为什么动态调用链和静态调用链都进行了缓存,这和前面讲解的CallbackFilter是息息相关的,因为上述代码最后使用fixedInterceptorOffset记录了当前静态调用链在数组中存储的位置,我们前面也讲了,Enhancer可以通过CallbackFilter返回的整数值来动态的指定从当前对象Callback数组中的第几个环绕逻辑开始织入,这里就会使用到fixedInterceptorOffset。从上述代码中可以看出,用户自定义的调用链是在DynamicAdvisedInterceptor中生成的(关于静态调用链的生成实际上是同样的逻辑,只不过静态调用链会被缓存),这里我们看看DynamicAdvisedInterceptor的实现源码:

Spring Aop之Cglib实现原理详解
Spring Aop之Cglib实现原理详解

这里intercept()方法里主要逻辑有两点:①为目标对象生成切面逻辑调用链;②通过切面逻辑对目标对象进行环绕,并且进行调用。关于这两点,我们都会进行讲解,这里我们首先看看Cglib是如何生成调用链的,如下是getInterceptorsAndDynamicInterceptionAdvice()方法最终调用的源码,中间略过了部分比较简单的调用:

Spring Aop之Cglib实现原理详解
Spring Aop之Cglib实现原理详解

这里获取调用链的逻辑其实比较简单,其最终的目的就是将Advisor数组一个一个的封装为Interceptor对象。在进行Advisor封装的时候,这里分为了三种类型:

如果目标切面逻辑是一般的切面逻辑,即PointcutAdvisor,则会在运行时对目标方法进行动态匹配,因为前面可能存在还不能确认的是否应该应用切面逻辑的方法;如果切面逻辑是IntroductionAdvisor的,则将其封装为Interceptor类型的数组;如果以上两个都不是,说明切面逻辑可能是用户自定义的切面逻辑,这里就通过注册的AdvisorAdapter进行匹配,如果某个Adapter能够支持当前Advisor的转换,则调用其getInterceptor()方法将Advisor转换为MethodInterceptor返回。

下面我们看看Cglib是如何通过生成的切面调用链将目标对象进行环绕的。前面我们讲了,将切面逻辑进行织入的逻辑在CglibMethodInvocation中,实际上其调用逻辑在其proceed()方法中,这里我们直接看该方法的源码:

Spring Aop之Cglib实现原理详解

这里proceed()方法的逻辑比较简单,其使用一个索引记录了当前正在调用的Interceptor在调用链中的位置,并且依次对调用链进行调用,从而实现将切面逻辑织入目标对象的目的。这里最终对目标对象的调用的逻辑在invokeJoinpoint()方法中。

3. 小结

本文首先讲解Spring是如何通过配置的参数来选择使用哪种代理方式的,然后重点讲解了Spring Aop是如何使用Cglib代理实现代理逻辑的织入的。