按照自己的思路研究Spring AOP源码【2】
问题的提出
上面这篇文章介绍了Spring AOP源码的核心流程,根据上面这篇文章提出的问题,我们继续来探讨一下为什么通知顺序是不一样的。
首先我们看一下新版本(5.3.5-SNAPSHOT)的通知顺序与输出结果,如下图:
顺序:Around Before After AfterReturning
输出如下:
===== Around before =====
===== Before =====
do service
===== AfterReturning =====
===== After =====
===== Around after =====
我们再来看一下旧版本(5.2.6.RELEASE)的通知顺序与输出结果,如下图:
顺序:AfterReturning After Around Before
===== Around before =====
===== Before =====
do service
===== Around after =====
===== After =====
===== AfterReturning =====
我们看到不同的顺序是对结果的输出也是有影响的
还有一个就是对After通知是否执行的影响,我们都知道After通知的定义是不管方法有没有抛异常,它都会执行
但是如果我们在Before通知或者Around通知中抛一个异常,那上面的两种排序对于After通知是否执行是不一样的,具体的执行结果我们来看一下:
我们先在Before通知中抛一个异常,代码如下:
@Before("pointCut()")
public void methodBefore() {
System.out.println("===== Before =====");
throw new RuntimeException();
}
下面是不同版本的执行结果:
新版本如下:
===== Around before =====
===== Before =====
Exception in thread "main" java.lang.RuntimeException
老版本如下:
===== Around before =====
===== Before =====
===== After =====
Exception in thread "main" java.lang.RuntimeException
从上面的结果可以看出,新版本的After通知是没有执行的,而老版本的After通知是执行了的,这就是通知顺序所导致的后果,所以小伙伴们在开发时碰到此类问题时,可以往这方面想想喔~
哪一步导致了顺序的改变
我们以旧版本来debug,原因到后面就知道了,所以以下的debug流程是基于旧版本的,我们来看看这个顺序是一直不变的呢,还是在某个方法执行之后发生了变化
我们就从JdkDynamicAopProxy.invoke
开始,一步一步的debug
,一起来看看顺序的变化。
在debug
的时候,有一个极其方便的技巧,那就是:我画出的那个图标:drop frame
,这个功能就是返回上一个函数,这样的话就不用重新运行然后重新打断点,就相当于一个后悔药,果然程序的世界和现实的世界是不一样的,你可以为所欲为,好,我们要做好心理准备开始debug
了。
首先,我们看一下这个chain
是怎么生成的,进到AdvisedSupport.getInterceptorsAndDynamicInterceptionAdvice
:
我们可以看到这个链通过getInterceptorsAndDynamicInterceptionAdvice
方法获得,并放到了一个缓存里
进到DefaultAdvisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice
:
看到有个advisors
数组,最终的链就是根据这个数组得来的,发现这个顺序就是最终顺序,可以看到这个数组是由config.getAdvisors()
得来的
进到AdvisedSupport.getAdvisors
:
我们可以看到返回的数组是this.advisorArray
,那我们来猜一下谁会把这个数组填充好
尝试在AdvisedSupport
这个类里搜索addAdvisor
,发现有11处:
在这些地方都打上断点
,然后重新运行,看看具体会运行到哪个位置
发现是在AdvisedSupport.addAdvisors(Advisor... advisors)
这个方法:
这一步的结果还是最终顺序,我们想知道谁调用了这个方法,这时候就可以用drop frame
了
可以看到在AbstractAutoProxyCreator.createProxy
调用了此方法:
可以看到数据是由specificInterceptors
得来的,并且依旧是最终顺序,继续drop frame
可以看到在AbstractAutoProxyCreator.wrapIfNecessary
调用了此方法:
可以看到调用getAdvicesAndAdvisorsForBean
这个方法获得了数组
进到AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean
这个方法:
进到AbstractAdvisorAutoProxyCreator.findEligibleAdvisors
这个方法:
在这里,我们发现顺序和最终顺序不一样了,终于找到你
来看一下后面的返回结果:
从上面的结果,我们可以猜测是sortAdvisors
这个方法改变了顺序
进到AbstractAdvisorAutoProxyCreator.sortAdvisors
这个方法:
从上图可以看出,是PartialOrder.sort(partiallyComparableAdvisors)
这个方法改变了顺序
为了确认一下,我们看一下新版的顺序是怎么样的
AbstractAdvisorAutoProxyCreator.findEligibleAdvisors
中sortAdvisors
之前的结果:
AbstractAdvisorAutoProxyCreator.findEligibleAdvisors
中sortAdvisors
之后的结果:
经观察,新版的顺序没有变化
AbstractAdvisorAutoProxyCreator.sortAdvisors()
方法
现在我们知道是哪个地方把顺序改变了,那我们就看一下PartialOrder.sort
这个方法
public class PartialOrder {
public static <T extends PartialComparable> List<T> sort(List<T> objects) {
// lists of size 0 or 1 don't need any sorting
if (objects.size() < 2) {
return objects;
}
// ??? we might want to optimize a few other cases of small size
// ??? I don't like creating this data structure, but it does give good
// ??? separation of concerns.
List<SortObject<T>> sortList = new LinkedList<SortObject<T>>();
for (Iterator<T> i = objects.iterator(); i.hasNext();) {
addNewPartialComparable(sortList, i.next());
}
// System.out.println(sortList);
// now we have built our directed graph
// use a simple sort algorithm from here
// can increase efficiency later
// List ret = new ArrayList(objects.size());
final int N = objects.size();
for (int index = 0; index < N; index++) {
// System.out.println(sortList);
// System.out.println("-->" + ret);
SortObject<T> leastWithNoSmallers = null;
for (SortObject<T> so: sortList) {
if (so.hasNoSmallerObjects()) {
if (leastWithNoSmallers == null || so.object.fallbackCompareTo(leastWithNoSmallers.object) < 0) {
leastWithNoSmallers = so;
}
}
}
if (leastWithNoSmallers == null) {
return null;
}
removeFromGraph(sortList, leastWithNoSmallers);
objects.set(index, leastWithNoSmallers.object);
}
return objects;
}
}
首先看一下下面的代码:
List<SortObject<T>> sortList = new LinkedList<SortObject<T>>();
for (Iterator<T> i = objects.iterator(); i.hasNext();) {
addNewPartialComparable(sortList, i.next());
}
我们从上面的代码可以看到,它为会每一个advice构造一个SortObject
结构:
private static class SortObject<T extends PartialComparable> {
T object;
List<SortObject<T>> smallerObjects = new LinkedList<SortObject<T>>();
List<SortObject<T>> biggerObjects = new LinkedList<SortObject<T>>();
}
object
是它自己本身,smallerObjects
包含了比它优先级高的advice,biggerObjects
包含了比它优先级低的advice
我们看一下构造结果是这样的:
根据这个结构,它是怎么排序的呢?看如下代码:
final int N = objects.size();
for (int index = 0; index < N; index++) {
// System.out.println(sortList);
// System.out.println("-->" + ret);
SortObject<T> leastWithNoSmallers = null;
for (SortObject<T> so: sortList) {
if (so.hasNoSmallerObjects()) {
if (leastWithNoSmallers == null || so.object.fallbackCompareTo(leastWithNoSmallers.object) < 0) {
leastWithNoSmallers = so;
}
}
}
if (leastWithNoSmallers == null) {
return null;
}
removeFromGraph(sortList, leastWithNoSmallers);
objects.set(index, leastWithNoSmallers.object);
}
boolean hasNoSmallerObjects() {
return smallerObjects.size() == 0;
}
每次找到smallerObjects对象size为0的对象,也就是此时它是优先级最高的
从上面的结果图可以看出,首先是ExposeInvocationInterceptor这个类,这个类是个扩展的advisor,它优先级最高,所以它的smallerObjects的大小为0,
所以它的顺序就是0,它被敲定之后,就会被移除掉,别人的smallerObjects和biggerObjects会把它移除掉,
然后AspectJAfterReturningAdvice的smallerObjects就会删除它,它的smallerObjects的size就会变成0,之后它就是顺序1
依次类推,AspectJAfterAdvice为顺序2 AspectJAroundAdvice为顺序3 最后AspectJMethodBeforeAdvice为顺序4。
那我们再来看一下SortObject
是怎么生成的,看下面的代码:
public class PartialOrder {
private static <T extends PartialComparable> void addNewPartialComparable(List<SortObject<T>> graph, T o) {
SortObject<T> so = new SortObject<T>(o);
for (Iterator<SortObject<T>> i = graph.iterator(); i.hasNext();) {
SortObject<T> other = i.next();
so.addDirectedLinks(other);
}
graph.add(so);
}
}
private static class SortObject<T extends PartialComparable> {
void addDirectedLinks(SortObject<T> other) {
int cmp = object.compareTo(other.object);
if (cmp == 0) {
return;
}
if (cmp > 0) {
this.smallerObjects.add(other);
other.biggerObjects.add(this);
} else {
this.biggerObjects.add(other);
other.smallerObjects.add(this);
}
}
}
从上面代码,我们可以看出,其实就是根据compareTo
方法进行比较,以此来判断添加到谁的smallerObjects
和biggerObjects
里面
关于compareTo
的调用,最终跟踪到了如下方法(同一切面下advice优先级的比较):
class AspectJPrecedenceComparator implements Comparator<Advisor> {
private int comparePrecedenceWithinAspect(Advisor advisor1, Advisor advisor2) {
boolean oneOrOtherIsAfterAdvice =
(AspectJAopUtils.isAfterAdvice(advisor1) || AspectJAopUtils.isAfterAdvice(advisor2));
int adviceDeclarationOrderDelta = getAspectDeclarationOrder(advisor1) - getAspectDeclarationOrder(advisor2);
if (oneOrOtherIsAfterAdvice) {
// the advice declared last has higher precedence
if (adviceDeclarationOrderDelta < 0) {
// advice1 was declared before advice2
// so advice1 has lower precedence
return LOWER_PRECEDENCE;
}
else if (adviceDeclarationOrderDelta == 0) {
return SAME_PRECEDENCE;
}
else {
return HIGHER_PRECEDENCE;
}
}
else {
// the advice declared first has higher precedence
if (adviceDeclarationOrderDelta < 0) {
// advice1 was declared before advice2
// so advice1 has higher precedence
return HIGHER_PRECEDENCE;
}
else if (adviceDeclarationOrderDelta == 0) {
return SAME_PRECEDENCE;
}
else {
return LOWER_PRECEDENCE;
}
}
}
private int getAspectDeclarationOrder(Advisor anAdvisor) {
AspectJPrecedenceInformation precedenceInfo =
AspectJAopUtils.getAspectJPrecedenceInformationFor(anAdvisor);
if (precedenceInfo != null) {
return precedenceInfo.getDeclarationOrder();
}
else {
return 0;
}
}
}
public abstract class InstantiationModelAwarePointcutAdvisorImpl {
@Override
public int getDeclarationOrder() {
return this.declarationOrder;
}
}
可以看到,其实比较的就是declarationOrder
这个字段
通过查看调用链,查看在哪里赋值了这个字段,如下图:
发现是ReflectiveAspectJAdvisorFactory.getAdvisors
中赋的值,如下图:
在旧版中,赋值的大小是advisors的size
由于advisor一个一个的被添加进去的,所以它们的值依次是0,1,2,3,验证结果如下图:
我们再来看一下新版的赋值:
我们看到新版的赋值都是0,这样的话,那大家的优先级都是一样的,所以就是按照默认顺序来进行执行的,
那这个默认的顺序又是怎么来的呢
通知是从切面的advice
方法提取出来的,并做了一下排序,具体如下:
来看一下排序的比较器:
那么比较器是怎么比较的呢
public class ConvertingComparator<S, T> implements Comparator<S> {
@Override
public int compare(S o1, S o2) {
T c1 = this.converter.convert(o1);
T c2 = this.converter.convert(o2);
return this.comparator.compare(c1, c2);
}
}
public class InstanceComparator<T> implements Comparator<T> {
@Override
public int compare(T o1, T o2) {
int i1 = getOrder(o1);
int i2 = getOrder(o2);
return (Integer.compare(i1, i2));
}
private int getOrder(@Nullable T object) {
if (object != null) {
for (int i = 0; i < this.instanceOrder.length; i++) {
if (this.instanceOrder[i].isInstance(object)) {
return i;
}
}
}
return this.instanceOrder.length;
}
public InstanceComparator(Class<?>... instanceOrder) {
Assert.notNull(instanceOrder, "'instanceOrder' array must not be null");
this.instanceOrder = instanceOrder;
}
}
从上面可以分析出,就是在instanceOrder这个数组里面的位置,而这个又是通过下面的构造函数赋值的
new InstanceComparator<>(Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class)
所以就是按照这个顺序来排序的
总结
是因为导致顺序不一致的呢?是spring的版本导致的,如下图:
低版本赋予了优先级,而高版本的没有赋予优先级,采用的默认顺序,那么默认循序是什么呢,如下图:
按照自己的思路研究Spring AOP源码【2】的更多相关文章
- 按照自己的思路去研究Spring AOP源码【1】
目录 一个例子 Spring AOP 原理 从@EnableAspectJAutoProxy注解入手 什么时候会创建代理对象? 方法执行时怎么实现拦截的? 总结 问题 参考 一个例子 // 定义一个切 ...
- 框架源码系列十:Spring AOP(AOP的核心概念回顾、Spring中AOP的用法、Spring AOP 源码学习)
一.AOP的核心概念回顾 https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html#a ...
- 最简 Spring AOP 源码分析!
前言 最近在研究 Spring 源码,Spring 最核心的功能就是 IOC 容器和 AOP.本文定位是以最简的方式,分析 Spring AOP 源码. 基本概念 上面的思维导图能够概括了 Sprin ...
- 5.2 Spring5源码--Spring AOP源码分析二
目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...
- 5.2 spring5源码--spring AOP源码分析二--切面的配置方式
目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...
- spring AOP源码分析(三)
在上一篇文章 spring AOP源码分析(二)中,我们已经知道如何生成一个代理对象了,那么当代理对象调用代理方法时,增强行为也就是拦截器是如何发挥作用的呢?接下来我们将介绍JDK动态代理和cglib ...
- Spring AOP 源码分析 - 拦截器链的执行过程
1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...
- Spring AOP 源码分析 - 创建代理对象
1.简介 在上一篇文章中,我分析了 Spring 是如何为目标 bean 筛选合适的通知器的.现在通知器选好了,接下来就要通过代理的方式将通知器(Advisor)所持有的通知(Advice)织入到 b ...
- Spring AOP 源码分析 - 筛选合适的通知器
1.简介 从本篇文章开始,我将会对 Spring AOP 部分的源码进行分析.本文是 Spring AOP 源码分析系列文章的第二篇,本文主要分析 Spring AOP 是如何为目标 bean 筛选出 ...
随机推荐
- css导航条的设计
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="U ...
- python-3-2
一 切片 1.切片是list取值的一种方式 列子: nums = ['a','b','c','d','e','f','h','g','k','l','kk','nn','ee'] 取b和c元素出来 p ...
- tips 【总结】
需求 移入a标签把对应的详情显示出来并且根据位置判断,当前详情是否超出父级可视区范围,如果超出就定位的距离方向应该正好在父级可视区范围内 需求分析: 需要用到: offsetLeft 获取外边框到 ...
- 妙味课上利用splice进行数组去重为什么要 j--
var arr = [ 1,2,2,4,4,5,8,8,9,0,4,4 ]; for ( var i=0; i<arr.length; i++ ) { for ( var j=i+1; j< ...
- 微信小程序--简约风博客小程序(基于云开发 - 全开源)
微信小程序--简约风博客小程序(基于云开发 - 全开源) 项目启动纯属突发奇想,想看看博客小程序,例如wehalo博客小程序,但是感觉自建平台还要浪费自己的服务器算力,还没有访问量,省省吧. 本着白嫖 ...
- Linux标准输入、重定向与参数传递
Linux标准输入.重定向与参数传递 按惯例,每当运行一个新程序时,所有shell都为其打开3个文件描述符,即标准输入.标准输出以及标准错误.如果不做特殊处理,例如就像简单的命令ls,则这三个描述符都 ...
- 带你全面认识CMMI V2.0(三)——实践域
实践域以往被称为称为"过程域",如:配置管理,现在叫做"实践域".对于2.0版,则有25个适用的实践域.与以前版本的CMMI模型一样,"实践域&quo ...
- Istio最佳实践系列:如何实现方法级调用跟踪?
赵化冰,腾讯云高级工程师,Istio Member,ServiceMesher 管理委员,Istio 项目贡献者,热衷于开源.网络和云计算.目前主要从事服务网格的开源和研发工作. 引言 TCM(Ten ...
- Ansible(1)- 简单介绍
什么是 Ansible 开源部署工具,也是一个自动化运维工具 开发语言:Python Ansible 的特性 模块化部署管理:调用特定的模块,完成特定任务 三个关键模块:Paramiko(python ...
- Python学习从入门到放弃?我不允许!!!
嗨,大家好 这里是汐仔 很多人都说学习python学习python,打开书本,三分钟,从入门到放弃. 这怎么可以!!!大家能选择python的原因可能是看它既简单,好入门,现在俨然是语言中的一匹黑马. ...