目录
第二次排序,获取到跟当前bean匹配的advisor列表之后
当同一个切面里面有多个相同类型的通知方法的时候,先执行哪个呢?
前言
我们知道可以通过注解的方式来定义通知方法,如 @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 决定的,下面我们还是用流程图的方式看一下它是的排序规则。图片可能看不清,流程图链接如下 AspectJPrecedenceComparator 排序比较器https://www.processon.com/view/link/614ff2e90e3e74520cb6c47dhttps://www.processon.com/view/link/614ff2e90e3e74520cb6c47d
这个排序比较器的流程看着挺复杂,但是总结下来还是很简单的,Spring 对 advisor 列表排序所用的排序器为 AspectJPrecedenceComparator,但是它其实是依赖 advisorComparator 进行排序,advisorComparator 是 AnnotationAwareOrderComparator 类型的排序器,它的排序规则如下
- 首先尝试根据是否实现了 PriorityOrdered 接口排序,实现该接口的优先
- 如果实现了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。可是为什么要这么设计呢?这跟各个通知方法的增强逻辑有关,下一篇文章将探讨被代理对象执行切点方法时各个增强方法的执行流程。