工作里写切面,十个里有八个会撞上这个问题:
一个 @Aspect 类里塞了 @Around、@Before、@After 好几个方法,都切同一个目标方法。跑起来一看输出顺序,和自己在文件里写的顺序对不上。
谁先谁后,全靠猜。猜错了,日志顺序乱了是小事,事务边界挪了位置才是大事。
这篇直接把排序源码翻出来。规则就两条,看完不用再猜。
一、先过一个筛子:不是所有方法都参战
解析切面时,Spring 先做一次过滤。
单独定义 @Pointcut 的方法,直接排除——它只是个切点声明,不是切面逻辑,没资格参与排序。
剩下的 @Around、@Before、@After 方法,才是候选。
二、第一级排序:注解类型定生死
排序用的比较器在类里的 static 块初始化,规则的第一级看注解类型:
@Around 排最前,@Before、@After 依次往后,再到 @AfterReturning、@AfterThrowing。
注意,这和你把方法写在文件的哪个位置没有任何关系。
两个方法切同一个目标,@Around 的方法写在文件后面、@Before 的写在前面——执行时 around 依然先跑。文件顺序在注解类型面前一文不值。
三、第二级排序:方法名字符串比大小
那两个方法用同一个注解呢?比如两个 @Before?
按方法名的字符串比较排序,小的排前面、先执行。
底层就是 methodName.compareTo() 那套字符串比较规则。方法名叫 zhouBefore1 和 zhouBefore2,"1"比"2"小,前者先执行。
这个规则简单到有点朴素,但 debug 验证过的结论就是它,没有任何隐藏逻辑。
四、为什么要有这套规则
往深想一层:切面方法最终会变成拦截器链,一个接一个 proceed() 下去。链上的顺序就是执行顺序——所以排序发生在生成 Advisor 的时候,不是调用的时候。
这也解释了为什么文件书写顺序不算数:Spring 从来没把"你写在哪一行"当成排序依据,它只认注解类型和方法名。
多个切面切同一个方法时还有更外层的排序规则(@Order 登场),那是下一篇的内容,这里先记住单切面内部这两级。
五、一张决策图带走
碰到顺序问题,按这个顺序判断:
注解不同?@Around @Before @After @AfterReturning @AfterThrowing,注解类型定序
注解相同?方法名字符串比大小,小的先执行
以后再看到切面输出顺序"不对",先查这两条,九成问题当场解决。
写在最后
两个字总结这套规则:注解优先,方法名兜底。
不过这是单个切面内部的排序。真实项目里往往是多个切面、多个 Advisor Bean 混在一起,那时候就轮到 @Order 出场了——而 @Order 有个坑:标在方法上根本不生效。下一篇用三个 debug 实验说话。