Spring AOP的实现原理 ?
AOP的实现,最关键的有两步:
- 得到代理对象
- 利用递归责任链执行前后置通知及目标方法
IOC容器初始化时,对于涉及AOP操作的目标类,其实Spring返回的是代理对象,而不是目标类的实例。至于Spring是如何创建AOP代理对象的,这里不做讨论,我们只讨论得到代理对象后的链式调用流程。
代理对象proxy其实包含了很多东西,比如:
- 目标对象
- 增强器
- ...
之前我曾经写过一篇动态代理相关的回答: Java 动态代理作用是什么?
大概就是讲了以下两点:
- 代理对象最终都会间接调用目标对象的同名方法,比如proxy.add() --> target.add()
- 但代理对象允许在调用add()前后添加一些增强代码,作为功能扩展
即,调用代理对象的方法最终都会“转嫁”成调用目标方法,但是在调用前后会执行一些其他操作,我称这些其他操作为“增强代码”,本质上就是上面提到的 增强器。
代理对象方法 = 拦截器链 + 目标对象方法
比如,JDK动态代理中,我们可以在invoke()方法中得到target并调用target.add(),并在前后加增强代码
AOP返回的代理对象也不例外。
现在假设代理对象proxy调用了某个方法,而这个方法会触发CglibAopProxy.intercept()。先不要理会为啥会触发这个方法,反正人家就是这样设定的。我们来看看intercept()方法:
intercept():
- 没有拦截器链
- 直接执行目标方法
- 有拦截器链
- 传入拦截器链和目标对象,最终new CglibMethodInvocation(...).proceed()
我们主要考虑有拦截器链的情况。
intercept()说穿了,就干了两件事:
- 收集拦截器,做成链
- 把拦截器链和目标对象等传入,执行new CglibMethodInvocation(...).proceed()
new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
这里new了一个CglibMethodInvocation对象,你就把它理解成一个大杂烩,反正AOP功能需要的材料都在这里了,包括目标对象、拦截器链啥的。
我们来看看它的proceed()方法干了啥:
简化后的示意图:
也就是说,只要拦截器链没执行完,就不会执行目标方法。即:
- 先执行全部的拦截器
- 最后执行 目标方法
我知道你看到这里,会有什么疑问:
既然拦截器都在目标方法前执行,怎么会出现AOP这种汉堡包式的“包夹”调用顺序?
before...
target.add()
afterReturning... ...
别急,接下来就是见证奇迹的时刻,我们看看拦截器的invoke()干了啥。
刚才说过了,拦截器是对增强器的包装。我们增强方法有哪些来着?Before/AfterReturning...等等,所以拦截器肯定也有对应的Before/AfterReturning...
我们先看Before拦截器:
我们发现:
- 先调用了this.advice.before()。即,反射执行@Before方法。
- 再调用了mi.proceed()。这个mi,就是我上一个截图传入的this,也就是CglibMethodInvocation对象。
再看After 拦截器:
- 先调用mi.proceed()
- 后反射调用@After方法
先别管具体上下文环境以及方法含义,我就问你,单纯看语句调用顺序,Before拦截器和After拦截器有何不同?
答案是:
Before拦截器是先反射调用@Before,再调用 mi.proceed()。
而After拦截器是先调用mi.proceed(),再反射调用@AfterRetruning方法。
其实,只有Before是特殊的,其他拦截器都是先调用proceed(),再反射调用通知方法。
为什么Before拦截器是异类?因为这么多拦截器中,只有Before的通知方法是在目标方法前。所以这个差异,肯定和AOP的汉堡包式的“包夹”调用顺序有莫大关系!Before拦截器这种与众不同的语句顺序,导致了它可以出现在目标对象前执行。
我去,有点晕...
其实这里开始,就进入递归了。递归有时是比较晕的。
为了帮大家理清调用顺序,我画了一张图。不过艺术家的画,有时比较难懂,所以我要先给大家解读一下我的作画风格。比如一个方法如果有两句:
我会画成下面这样:
左边的代表上面的(先执行),右边的代表下面的(后执行)。
好,了解我画图的风格后,上主菜。
AOP递归责任链:
跟着调用栈,可以看到顺序是before---target.add()---after...
流程解读:
- proxy.add()触发CglibAopProxy.intercept()
- intercept()
- 获取所有的拦截器,排好序后做出拦截器链(顺序和AOP执行顺序相反,before反而放链的末尾!)
- 传入拦截器链和目标对象,new CglibMethodInvocation()并调用proceed()
- proceed()先执行全部拦截器,最后执行目标方法
- 目标方法的return是整个递归责任链的精华所在,就像一个弹簧,被压到最大限度,开始return了。所以,原路返回,执行每个拦截器invoke()方法中两个语句的下一句
- 又由于Before拦截器是先反射执行通知方法后调用proceed(),而其他拦截器是先proceed()后反射,所以形成了“包夹”调用顺序
所以,虽然说拦截器全部执行完毕,才执行目标方法,但是拦截器的invoke()方法其实有两句语句,上一句只是递归调用下一个拦截器,只有等反射执行目标方法后,回来的路上才会真正执行通知方法!
计算机小鹏: 我的cuda是11.8版本怎么办
yygr: 我搜了一下,是说G1初始的比例是1:2,过程中的比例是动态变化的,看的这篇文章,可以再多看看其它资料: https://www.sohu.com/a/460663353_185201
YuKuiKuiZi: 最终解决方法有效,感谢
下水道里的老鼠。: 想请问博主一个问题,就是之前说的新生代和老年代的大小是1:2,在G1收集器这里还是这样吗
XXSec.: 为什么我的chorme浏览器 每次打开这个下载图标都从别的地方弹出来啊 每次打开浏览器的时候都是 烦死了