Spring AOP 动态代理执行流程源码分析

489 阅读4分钟

动态代理对象执行增强方法时,到底是如何一层一层的执行切面方法的?本文会从源码层面来解析这个问题。

对于动态代理生成过程有疑问的jym可以参考我的这篇文章:Spring AOP 创建动态代理源码分析

执行动态代理方法的入口

很多朋友也想去debug执行流程的源码,但是却不知道方法的入口在哪里,断点打在哪里。

我的这篇文章Spring AOP 创建动态代理源码分析 的最后,分析了2种代理生成的对象,分别是CglibAopProxyJdkDynamicAopProxy

由于下面内容涉及到源码,提前说明我的Springboot版本为2.4.3,可能版本不同源码略有差异但大致差异不大。 但是非常老的Springboot版本它的AOP源码是完全不同的,具体可以参考我的这篇文章:Spring AOP 执行顺序问题。老版的AOP,执行顺序都不同,所以源码差异也比较大。

jdk动态代理,直接打断点在invoke方法即可。

image.png

cglib动态代理,直接打断点在DynamicAdvisedInterceptor类的intercept方法即可。

image.png

jdk动态代理

jdk.png

直接定位到invoke方法的关键地方,如果有切面则会创建ReflectiveMethodInvocation这个对象,执行proceed()方法。

至于这个对象是什么执行的这个方法是什么我们马上再说。

cglib动态代理

cglib.png

直接定位到intercept方法的关键地方,如果有切面则会创建CglibMethodInvocation这个对象,执行proceed()方法。

extend.png

proceed.png

观察了一下CglibMethodInvocation对象,它竟然继承了ReflectiveMethodInvocation,而且proceed()方法就是父类的方法。

至此,我们发现,jdk动态代理和cglib动态代理最后都走到了ReflectiveMethodInvocation对象的 proceed()方法。

于是我们现在可以好好的来分析proceed()方法了。

源码+案列

由于光读源码显得粗糙乏味不易理解,所以我还是打算以案列结合源码的方式来进行描述。

案列准备

需要的代理类TestController 以及切面类MethodAop ,并且附上最后的执行结果。

controller.png

aopM.png

res.png

源码解析

proceed()方法

proceed2.png

根据案列我们有6个切面,分别是暴露切面的ExposeInvocationInterceptor切面,其次按照顺序,分别是 around、before、after、afterReturning、afterThrowing,对应的类分别是MethodInterceptor接口的实现类AspectJAroundAdviceMethodBeforeAdviceInterceptorAspectJAfterAdviceAfterReturningAdviceInterceptorAspectJAfterThrowingAdvice

proceed3.png

ExposeInvocationInterceptor切面的invoke方法

proceed4.png

很多朋友会疑惑为什么第一个切面是这个切面,源码方面很简单就是把ReflectiveMethodInvocation这个对象放入了threaLocal中,至于什么地方会用到我们后续再说。

proceed5.png

AspectJAroundAdvice切面的invoke方法

around.png

common.png

invokeAdviceMethod方法是一个共通方法,主要由三个重要方法组成:

1.argBinding方法:主要封装反射调用的参数。看此方法的入参就知道封装的是around切面的joinpoint,afterReturn切面的返回值,afterThrow切面的expection。

2.invokeAdviceMethodWithGivenArgs方法:见名知意,就是通过反射以及入参调用对应的切面方法。

3.getJoinPoint方法:为around切面准备joinPoint的入参。

around3.png

本文就不仔细分析这三个方法了,有兴趣的朋友可以自行去阅读源码。

回归正文,反射调用around切面后会发生什么?

around2.png

此时执行完around前,之后又会再次执行proceed()方法。

在此我想提出一个问题,如果我around方法没有入参也就是不执行proceed()方法会有什么情况发生? 其实很简单,没有了proceed()方法,自然不会再走对应的切面链条,也不会再走对应的业务方法,执行完around方法中的逻辑后就结束了。所以一定要注意around方法中必须要有joinPoint.proceed();这段逻辑。

MethodBeforeAdviceInterceptor切面的invoke方法

before.png

before2.png

AspectJAfterAdvice切面的invoke方法

after.png

AfterReturningAdviceInterceptor切面的invoke方法

return.png

return2.png

AspectJAfterThrowingAdvice切面的invoke方法

throw.png

调用实际业务方法

proceed6.png

invoke.png

invoke1.png

调用实际方法结束以后,然后会一层一层往回走,先走afterThrowing,再走afterReturning,再走after,然后回到around切面中的joinPoint.proceed();,执行around后切面的内容。

总结

Spring AOP的实现真的十分巧妙,尤其invoke(this)这个方法,不断的包装方法,一层层的执行对应的切面,不得不感叹Spring AOP设计的精妙。

很多人都说Spring AOP的实现源码过于复杂,不如myBatis的Plugin组件,轻巧简洁。我个人觉得不同的设计用于不同的场景,Spring AOP的复杂源于它的通用性,而myBatis的Plugin采用jdk动态代理,只需要扩展个别接口,自然不需要过于繁杂。

感谢能看到这里的jym,本文从源码层面大致的讲解了切面的执行顺序,如果各位想要深入的了解,也需要自己好好的跟踪源码。

如果文中有错误的地方,还请各位不吝赐教!

c3ff758e76ba42399f5473607ebe535b_tplv-k3u1fbpfcp-zoom-in-crop-mark_3024_0_0_0.webp