问题的提出

上面这篇文章介绍了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.findEligibleAdvisorssortAdvisors之前的结果:

AbstractAdvisorAutoProxyCreator.findEligibleAdvisorssortAdvisors之后的结果:



经观察,新版的顺序没有变化

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方法进行比较,以此来判断添加到谁的smallerObjectsbiggerObjects里面

关于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)
所以就是按照这个顺序来排序的

总结

  1. 是因为导致顺序不一致的呢?是spring的版本导致的,如下图:

  2. 低版本赋予了优先级,而高版本的没有赋予优先级,采用的默认顺序,那么默认循序是什么呢,如下图:

按照自己的思路研究Spring AOP源码【2】的更多相关文章

  1. 按照自己的思路去研究Spring AOP源码【1】

    目录 一个例子 Spring AOP 原理 从@EnableAspectJAutoProxy注解入手 什么时候会创建代理对象? 方法执行时怎么实现拦截的? 总结 问题 参考 一个例子 // 定义一个切 ...

  2. 框架源码系列十:Spring AOP(AOP的核心概念回顾、Spring中AOP的用法、Spring AOP 源码学习)

    一.AOP的核心概念回顾 https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html#a ...

  3. 最简 Spring AOP 源码分析!

    前言 最近在研究 Spring 源码,Spring 最核心的功能就是 IOC 容器和 AOP.本文定位是以最简的方式,分析 Spring AOP 源码. 基本概念 上面的思维导图能够概括了 Sprin ...

  4. 5.2 Spring5源码--Spring AOP源码分析二

    目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...

  5. 5.2 spring5源码--spring AOP源码分析二--切面的配置方式

    目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...

  6. spring AOP源码分析(三)

    在上一篇文章 spring AOP源码分析(二)中,我们已经知道如何生成一个代理对象了,那么当代理对象调用代理方法时,增强行为也就是拦截器是如何发挥作用的呢?接下来我们将介绍JDK动态代理和cglib ...

  7. Spring AOP 源码分析 - 拦截器链的执行过程

    1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...

  8. Spring AOP 源码分析 - 创建代理对象

    1.简介 在上一篇文章中,我分析了 Spring 是如何为目标 bean 筛选合适的通知器的.现在通知器选好了,接下来就要通过代理的方式将通知器(Advisor)所持有的通知(Advice)织入到 b ...

  9. Spring AOP 源码分析 - 筛选合适的通知器

    1.简介 从本篇文章开始,我将会对 Spring AOP 部分的源码进行分析.本文是 Spring AOP 源码分析系列文章的第二篇,本文主要分析 Spring AOP 是如何为目标 bean 筛选出 ...

随机推荐

  1. P1601_A+B Problem(高精)(JAVA语言)

    思路:BigInteger first blood! //四行搞定 题目背景 无 题目描述 高精度加法,x相当于a+b problem,[b][color=red]不用考虑负数[/color][/b] ...

  2. 阿里一面CyclicBarrier和CountDownLatch的区别是啥

    引言 前面一篇文章我们<Java高并发编程基础三大利器之CountDownLatch>它有一个缺点,就是它的计数器只能够使用一次,也就是说当计数器(state)减到为 0的时候,如果 再有 ...

  3. C++中的间接宏函数

    宏函数对于每个C++程序员都决不陌生,就算是初出茅庐的C++程序员也知道如何定义.使用宏函数.   但是当初学者看到类似于以下这种宏函数嵌套的时候,可能还是会比较嘀咕, #define CONVERT ...

  4. 计算机体系结构——CH5 标量处理机

    计算机体系结构--CH5 标量处理机 右键点击查看图像,查看清晰图像 X-mind 计算机体系结构--CH5 标量处理机 先行控制技术 指令得重叠执行方式 顺序执行方式 一次重叠执行方式 二次重叠技术 ...

  5. PAT (Advanced Level) Practice 1005 Spell It Right (20 分) 凌宸1642

    PAT (Advanced Level) Practice 1005 Spell It Right (20 分) 凌宸1642 题目描述: Given a non-negative integer N ...

  6. 运维趋势2019年总结,运维就是要做到"技多不压身"

    2019年 在互联网这个行业中对运维的要求越来越来越高,比如2015.16年的时候,运维只是做一些人力投入的事情,比如重启个服务,搭建一个nginx,mysql主从服务,简单的优化一下,就可以上线了, ...

  7. OxyPlot.SkiaSharp显示中文乱码的问题

    oxyplot 图表控件功能强大,使用很广泛.最近考虑到性能使用OxyPlot.SkiaSharp替代OxyPlot.WPF,曲线图表初步测试,性能提升近10倍左右.基于SkiaSharp图形引擎的一 ...

  8. web编辑工具 - Brackets - 强大免费的开源跨平台Web前端开发工具IDE

      简单使用可以参考: https://blog.csdn.net/melon19931226/article/details/68066971/ https://www.iplaysoft.com/ ...

  9. 软工AI Bot NABCD分析

    目标: 打造一个基于大数据的 IT 问答机器人服务,通过运用人工和AI 技术,极大提高问答产品的用户满意度. 适合高校<软件工程>,<人工智能>课程作为结对编程或者团队项目. ...

  10. BUAA_OS lab4 难点梳理

    BUAA_OS lab4 难点梳理 lab4体会到了OS难度的飞升.实验需要掌握的重点有以下: 系统调用流程 进程通信机制 fork 本lab理解难度较高,接下来将以以上三部分分别梳理. 系统调用 概 ...