Spring AOP通知方法的执行顺序及说明

 更新时间:2026年03月18日 10:40:48   作者:Cison chen  
这篇文章主要介绍了Spring AOP通知方法的执行顺序及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

前言

我们知道可以通过注解的方式来定义通知方法,如 @Before,@After 等,那么当执行连接点方法的时候这些通知方法的执行的执行顺序是什么呢?

本篇文章就是探讨这个问题的,关于 advisor, pointCut,切面等定义以及生成 advisor 的时间点前面文章都已经介绍过,所以本篇只讨论 advisor 数组排序相关的内容。

第一次排序,遍历切面方法时排序

我们知道,生成 advisor 实例首先要解析切面类,更准确的说是解析通知方法上的注解,根据不同的注解生成不同的 pointCut 进而生成不同的 advisor 实例,所以先遍历哪个方法,就先生成该方法对应的 advisor 然后依次放入 advisor 数组中, 可以理解为这是对 advisor 数组的第一次排序。

    private List<Method> getAdvisorMethods(Class<?> aspectClass) {
		final List<Method> methods = new ArrayList<>();
		ReflectionUtils.doWithMethods(aspectClass, method -> {
			// Exclude pointcuts
			if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) {
				methods.add(method);
			}
		}, ReflectionUtils.USER_DECLARED_METHODS);
		if (methods.size() > 1) {
			methods.sort(METHOD_COMPARATOR);
		}
		return methods;
	}

其实上段获取排序的方法的逻辑很简单,就是通过反射获取到通知方法。此外,因为 @Pointcut 只是切点,并不是一个增强方法,所以不需要生成 advisor, 所以这里就将它排除了。然后我们发现排序使用了 METHOD_COMPARATOR, 所以具体怎么排序还是由该排序器决定的,接下来我们看一下它是如何实例化以及如何排序的。可以先看下面的流程图。

通过上面流程图我们知道,该排序比较器首先是按照 Around, Before, After, AfterReturning, AfterThrowing 等注解类型进行排序的,如果有相同类型的通知方法,再按照方法名称进行排序。

如此一来,后面在遍历通知方法生成 advisor 实例的时候也会按照这个顺序一个一个放入 advisor 列表中。

第二次排序,获取到跟当前bean匹配的advisor列表之后

    protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
		List<Advisor> candidateAdvisors = findCandidateAdvisors();
		List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
		extendAdvisors(eligibleAdvisors);
		if (!eligibleAdvisors.isEmpty()) {
            //对跟当前 bean 匹配的 advisor 列表进行排序
			eligibleAdvisors = sortAdvisors(eligibleAdvisors);
		}
		return eligibleAdvisors;
	}
    // AspectJAwareAdvisorAutoProxyCreator 重写的 sortAdvisors 方法
    protected List<Advisor> sortAdvisors(List<Advisor> advisors) {
		List<PartiallyComparableAdvisorHolder> partiallyComparableAdvisors = new ArrayList<>(advisors.size());
		for (Advisor advisor : advisors) {
			partiallyComparableAdvisors.add(
					new PartiallyComparableAdvisorHolder(advisor, DEFAULT_PRECEDENCE_COMPARATOR));
		}
		List<PartiallyComparableAdvisorHolder> sorted = PartialOrder.sort(partiallyComparableAdvisors);
		if (sorted != null) {
			List<Advisor> result = new ArrayList<>(advisors.size());
			for (PartiallyComparableAdvisorHolder pcAdvisor : sorted) {
				result.add(pcAdvisor.getAdvisor());
			}
			return result;
		}
		else {
			return super.sortAdvisors(advisors);
		}
	}

因为本篇文章主要是讨论排序,所以只针对排序的内容,从上面代码可以看出来,对 advisor 列表的排序主要是由DEFAULT_PRECEDENCE_COMPARATOR 决定的,下面我们还是用流程图的方式看一下它是的排序规则。

图片可能看不清,流程图链接如下:

https://www.processon.com/view/link/614ff2e90e3e74520cb6c47d

这个排序比较器的流程看着挺复杂,但是总结下来还是很简单的,Spring 对 advisor 列表排序所用的排序器为AspectJPrecedenceComparator,但是它其实是依赖advisorComparator 进行排序,advisorComparator 是AnnotationAwareOrderComparator 类型的排序器,它的排序规则如下

  1. 首先尝试根据是否实现了 PriorityOrdered 接口排序,实现该接口的优先
  2. 如果实现了Ordered 接口,则需要重写 getOrder() 方法,根据该方法返回值确定排序顺序。如果对象类上标注了 @Order 注解,则按照注解的属性值排序。否则返回 Ordered.LOWEST_PRECEDENCE 默认排在最后。

如果借助AnnotationAwareOrderComparator 类型的排序器没有排好顺序(存在order定义相同的情况),但是多个不同的 advisor 又在同一个切面,AspectJPrecedenceComparator 会调用 comparePrecedenceWithinAspect 通过declarationOrder 再次排序,这里需要注意一下,这个 declarationOrder 本来的意思是代表通知方法在切面中的声明顺序,根据这个顺序可以决定是先声明的通知先执行或者后执行。

只不过从 jdk7 之后,不支持通过类信息获取到声明顺序了,所以Spring 在实例化通过注解生成的 advisor 的时候都给设置成了 0,所以所有的advisor声明顺序都一样。那么这一步其实也就不生效了,并不会影响排序结果。

当同一个切面里面有多个相同类型的通知方法的时候,先执行哪个呢?

考虑到前面讨论的两步排序都没有处理这种情况,那么影响相同类型通知方法执行顺序的地方就只有获取通知方法的时候,先获取到哪个方法就会先生成对应的 advisor, 也就会先执行。

解析切面获取类中声明方法所调用的接口为Method[] declaredMethods = clazz.getDeclaredMethods();该方法返回的方法数组的顺序是按照由代码里声明的顺序决定的,所以在切面类中,那个通知在前面,就会先执行哪一个。

一般情况下各个通知方法的执行顺序

一般情况下我们也不会在同一个类中声明多个相同类型的增强方法,假设我们切面类现在 Around, Before, After, AfterReturning, AfterThrowing 这五种类型的通知方法各有一个,那么通过第一步排序我们获得的就是Around->Before->After->AfterReturning->AfterThrowing 这个顺序,此外注解类型的通知实例化出的 advisor 类型为 InstantiationModelAwarePointcutAdvisorImpl,它实现了 Order 接口,可以通过在切面也实现 Ordered 接口进而影响 advisor 在第二步排序的顺序。

但是因为没法单独对某一个通知方法设置 order, 所以通过给切面设置 order, 最后作用到这几个通知方法的效果是一样的,比如都变成了 1 或者都变成1000000,如果不设置的话,默认为Ordered.LOWEST_PRECEDENCE, 即最小优先级。

但无论是什么优先级,并不会影响这几个通知方法之间的执行顺序。

总结

目前为止,我们应该已经知道了 AOP 各种类型的增强方法的执行顺序是怎么样的了,即Around->Before->After->AfterReturning->AfterThrowing。可是为什么要这么设计呢?

这跟各个通知方法的增强逻辑有关,下一篇文章将探讨被代理对象执行切点方法时各个增强方法的执行流程。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 关于maven依赖 ${xxx.version}报错问题

    关于maven依赖 ${xxx.version}报错问题

    这篇文章主要介绍了关于maven依赖 ${xxx.version}报错问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • Java IO API实现获取路径信息并去除冗余信息

    Java IO API实现获取路径信息并去除冗余信息

    这篇文章主要为大家详细介绍了Java IO API实现获取路径信息并去除冗余信息,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2026-03-03
  • Springboot maven plugin插件原理及作用

    Springboot maven plugin插件原理及作用

    这篇文章主要介绍了Springboot maven plugin插件原理及作用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-10-10
  • Java查找不重复无序数组中是否存在两个数字的和为某个值

    Java查找不重复无序数组中是否存在两个数字的和为某个值

    今天小编就为大家分享一篇关于Java查找不重复无序数组中是否存在两个数字的和为某个值,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • Springboot整合quartz产生错误及解决方案

    Springboot整合quartz产生错误及解决方案

    这篇文章主要介绍了Springboot整合quartz产生错误及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • SpringMVC表单标签使用详解

    SpringMVC表单标签使用详解

    这篇文章主要为大家详细介绍了SpringMVC表单标签的使用方法,教大家如何用Spring封装的一系列表单标签
    2017-03-03
  • Maven插件构建Docker镜像的实现步骤

    Maven插件构建Docker镜像的实现步骤

    这篇文章主要介绍了Maven插件构建Docker镜像的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-10-10
  • Spring中的ConversionService源码解析

    Spring中的ConversionService源码解析

    这篇文章主要介绍了Spring中的ConversionService源码解析,ConversionService是类型转换服务的接口,从名字就可以看出ConverterRegistry是要实现转换器注册表的接口,添加和移除Converter和GenericConverter,需要的朋友可以参考下
    2023-11-11
  • 手把手带你用java搞定青蛙跳台阶

    手把手带你用java搞定青蛙跳台阶

    这篇文章主要给大家介绍了关于Java青蛙跳台阶问题的解决思路与代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-08-08
  • Java中关于OAuth2.0的原理分析

    Java中关于OAuth2.0的原理分析

    这篇文章主要介绍了Java中关于OAuth2.0的原理分析,OAuth是一个关于授权的开放网络标准,允许用户授权第三 方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容,需要的朋友可以参考下
    2023-09-09

最新评论