spring aop源码详解

spring 通过 aop 主要用于在调用接口的前后,做一些操作,例如打印日志等。

对 aop 有所了解的人都知道,aop 是通过代理模式实现的。在前面的文章《spring源码中设计模式的使用》中也写过。

当我们想在一个对象中的方法的前后,增加一些统一的逻辑时,可以根据这个对象生成一个代理对象。想要调用目标方法时,不使用原始对象,而使用代理对象来调用此方法。这个时候就可以在方法的前后增加一些自己的逻辑。

所以,我们首先看看 aop 中代理对象的生成过程。

我写了个切面类 LogAspect,用于在 PaymentService 接口的所有方法前后打印日志。本文以这个场景为例,详细讲解 spring aop 的源码。

@Slf4j
@Aspect
@Component
public class LogAspect {
    @Pointcut("execution(* cn.xujingyi.sample.service.PaymentService.*(..))")
    public void pointCut() { }

    @Before(value = "pointCut()")
    public void before(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        log.info("enter method[" + methodName + "] before advice. " +
                "current time > " + System.currentTimeMillis());
    }

    @After(value = "pointCut()")
    public void after(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        log.info("enter method[" + methodName + "] after advice. " +
                "current time > " + System.currentTimeMillis());
    }
}

创建代理对象

上一篇文章《spring ioc源码详解》中,详细讲解了 bean 的实例化、初始化的源码。但是在创建代理对象的地方跳过了。这里详细讲解下创建代理对象的源码。

回到 doCreateBean 的初始化 bean 的地方,可以发现此时这个待返回的 exposedObject,目前还是真实对象。就是在这个 initializeBean 方法中,生成并返回代理对象的。

spring aop源码详解_第1张图片

进入 initializeBean 方法。上一篇文章中也提了,在这个方法里,首先通过 BeanPostProcessors,在 bean 初始化之前做点事情,然后调用 bean 的初始化方法进行初始化,最后通过 BeanPostProcessors,在 bean 初始化之后做点事情。

spring aop源码详解_第2张图片

生成代理对象就是在这个 afterInitialization 中做的。在这个方法里面,循环遍历所有 BeanPostProcessor,调用它的 postProcessAfterInitialization 方法做后置处理。其中,用于生成代理对象的,就是 AnnotationAwareAspectJAutoProxyCreator,它的 proxyTargetClass 值为 true,且参数 advisorAdapterRegistry 中有三个适配器 adapters,就是后面用来将通知转化为拦截器的。

spring aop源码详解_第3张图片

进入 postProcessAfterInitialization 方法,

spring aop源码详解_第4张图片

真正进行处理的地方是 wrapIfNecessary 方法。里面有一段代码,很明显就是用来创建代理对象的。

spring aop源码详解_第5张图片

首先,它在里面根据我们的切面配置,获取对于的通知器,我们在切面中配置了 before 和 after 两个通知,这里就获取到了。还有一个默认拦截器,以及一个事务的通知器,事务这个是因为我对这个方法加了 @Transactional 注解,下一篇文章会讲事务的源码。

spring aop源码详解_第6张图片

然后就是调用 createProxy 方法创建代理对象了!

spring aop源码详解_第7张图片

这个方法里面,首先还是设置通知器等信息,然后设置了 frozen 属性,表示冻结配置,暂时不能修改配置了。最后调用 getProxy 获取代理对象。

spring aop源码详解_第8张图片

首先是获取 AopProxy 对象,发现默认使用的是 cglib 代理,而不是 jdk动态代理。进方法看下

spring aop源码详解_第9张图片

新版本是对实现类 PaymentServiceImpl 进行代理的,不是接口,所以默认使用的是 cglib 代理。以前老版本是对接口代理,默认使用的是 jdk 动态代理。

接下来就是调用 AopProxy 类的 getProxy 获取代理类了,这个属于 cglib 的内容了,就不看了。

spring aop源码详解_第10张图片

返回到 createProxy 的地方,此时已经获取到代理类了,从变量名上也可以看出来,这是个代理类。一步步返回,直到刚刚循环遍历 BeanPostProcessors 的地方,也可以看到返回结果是一个代理类。

spring aop源码详解_第11张图片

回到刚才调用 initializeBean 的地方,发现此时的 exposedObject 是代理类。

spring aop源码详解_第12张图片

接着,会触发一次 getSingleton,此时在缓存中查询不到,方法结束,将代理对象 exposedObject 返回。回到那个带回调函数的 getSingleton 方法中(上一篇讲 ioc 源码的文章中讲过),将当前的代理对象放入缓存

spring aop源码详解_第13张图片

 这个 singletonObjects 上一篇文章中讲过,是 spring 三级缓存中的第一级,也叫做单例缓存池,用于存放所有初始化完成的 bean。以后取 bean 也是在这个 singletonObjects 中获取的。

spring aop源码详解_第14张图片

通过截图可以看出,此时单例缓存池中,beanName 为 "paymentServiceImpl" 的 bean,真正存放的是一个 cglib 代理对象!

到这里为止,创建代理对象的整个流程,就讲解完了。接下来就是使用这个代理对象来调用方法了。

获取拦截器链

编写测试类,调用 PaymentService 的 pay 方法

spring aop源码详解_第15张图片

开启 debug,然后 F7 进入 pay 方法,发现进入的是 cglib 的代理类。上面我们讲过,对于 "paymentServiceImpl" 这个 bean 来说,单例缓存池中放的是代理对象。而我们通过 @Autowire 注解注入的属性,实际上也是通过 getBean 方法从单例缓存池中获取的,所以这里的 paymentService 是一个代理对象。也只有在使用代理对象调用方法时,才能在方法的前后做一些事情。

spring aop源码详解_第16张图片

往下继续看代码,发现了获取拦截器链

spring aop源码详解_第17张图片

这个就是我们在 LogAspect 类中定义的前置和后置通知对应的拦截器 。

进入 advised.getInterceptorsAndDynamicInterceptionAdvice 方法

首先获取 advice 适配器,适配器有3种,是用来将我们定义的 advice 转换成 intercepter 的,最终使用的 intercepter。

spring aop源码详解_第18张图片

然后获取所有的通知,可以看到有三个,一个是默认的,不用去管它。还有两个是我们在 LogAspect 中配置的前置和后置通知。

spring aop源码详解_第19张图片

然后遍历上面获取的所有通知,判断当前调用的方法,是否跟 pointcut 的 execution 表达式匹配,可以看到此处我调用的 pay 方法是匹配的,因为我在 execution 中写的是 PaymentService 中的所有方法。

spring aop源码详解_第20张图片

当确定当前调用的方法需要被拦截时,调用 registry.getInterceptors 将 advice 转化为 intercepter。这个 advice 就是我们在 LogAspect 中配置的 before 和 after 通知。

spring aop源码详解_第21张图片

对于每一个通知 advice,都循环遍历 adapters,通过 supportsAdvice 方法判断那个 adapter 符合这个 advice。例如当前的遍历到的 advice 是 AspectJMethodBeforeAdvice,则发现 MethodBeforeAdviceAdapter 跟它匹配。

spring aop源码详解_第22张图片

知道哪个 adapter 跟此通知匹配后,从此 adapter 中获取对应的拦截器 Intercepter。在此处就是 MethodBeforeAdviceInterceptor。之后将拦截器放入拦截器链,也就是 interceptorList 集合中。

spring aop源码详解_第23张图片

这里的 adapter,运用的是设计模式中的“适配器模式”。这个在前面的文章《spring源码中设计模式的使用》中也写过了。

到此为止,拦截器链就获取结束了。我画了个流程图:

spring aop源码详解_第24张图片

调用拦截器链和目标方法

获取了拦截器链后,就真正开始执行拦截器中的方法,并调用目标方法了。

spring aop源码详解_第25张图片

首先创建一个 CglibMethodInvocation,然后调用 proceed 方法,实际调用的是它的父类 ReflectiveMethodInvocation 中的 proceed 方法。我们进入 proceed 方法看看,这个是重点。

spring aop源码详解_第26张图片

currentInterceptorIndex 初始为 -1。interceptorsAndDynamicMethodMatchers 就是在前面获取的3个拦截器。通过判断 currentInterceptorIndex 是否等于 interceptorsAndDynamicMethodMatchers 的 (size - 1),来知道目前应该调用的是拦截器,还是目标方法。

然后就是循环这个 interceptorsAndDynamicMethodMatchers,一次调用拦截器的 invoke 方法

spring aop源码详解_第27张图片

比如当前是 MethodBeforeAdviceInterceptor, 进入它的 invoke 方法查看。它在调用目标方法之前,首先调用了拦截器中的方法

spring aop源码详解_第28张图片

具体是在这个 advice.before 方法中调用的,点进去看下,好几层,一直进入到 invokeAdviceMethodWithGivenArgs 方法中。

spring aop源码详解_第29张图片

这里运用了java 的反射机制。aspectJAdviceMethod 是个 LogAspect 中的 before 方法对应的 Method 对象,调用它的 invoke 方法,就相当于调用了 before 方法。

spring aop源码详解_第30张图片

所以,执行完 invoke,在日志中就可以看到我们定义的打印日志了。

执行完前置通知后,继续调用 proceed 方法,重新进入前面的 proceed 方法

spring aop源码详解_第31张图片

此时 currentInterceptorIndex 加了1,获取到的 advice 是 AspectjAfterAdvice。执行它的 invoke 方法。在这个方法中,可以看到,直接调用 proceed 方法,继续调用下一个拦截器。而在 finally 中,调用后置方法 invokeAdviceMethod!从这里也可以看出,后置通知肯定是会执行的。

spring aop源码详解_第32张图片

由于这个 AspectjAfterAdvice 已经是最后一个了,后面没有其他拦截器了,所以 proceed 方法中,直接调用真实的 pay 方法。

spring aop源码详解_第33张图片

从日志可以看出,目标方法已调用结束,里面我只是打印了一行日志。之后,就执行 finally 中的后置方法 invokeAdviceMethod

spring aop源码详解_第34张图片

一步步 debug,层数比较深,最后会走到我们定义的后置方法中。

spring aop源码详解_第35张图片

到这里为止,一个拦截方法的流程就全部走完了。

本文只讲了 before 和 after 通知。aop 还有两个通知,return 和 throw 通知没讲,因为平时用的 before 和 after 比较多。

还有一点要注意,after 通知最终是在 finally 中执行的,所以一定会执行。而 return 和 throw 就不一定了。

我们可以针对一个方法,做很多的拦截。spring 的事务功能也是通过 aop 来实现的。下一篇会详细讲解下 spring 事务的源码。

你可能感兴趣的:(spring系列,spring,java)