spring aop 通知执行顺序
一.问题
先来一个spring aop 切面代码示例

1 @Order(1) 2 @Component 3 @Aspect 4 public class Aspect2 { 5 6 @Before(value = "test.test.PointCuts.aopDemo()") 7 public void before(JoinPoint joinPoint) { 8 System.out.println("[Aspect2] before advise"); 9 } 10 @Before(value = "test.test.PointCuts.aopDemo()") 11 public void before2(JoinPoint joinPoint) { 12 System.out.println("[Aspect2] before advise2"); 13 } 14 15 @Around(value = "test.test.PointCuts.aopDemo()") 16 public void around(ProceedingJoinPoint pjp) throws Throwable{ 17 System.out.println("[Aspect2] around advise 1"); 18 pjp.proceed(); 19 System.out.println("[Aspect2] around advise2"); 20 } 21 @Around(value = "test.test.PointCuts.aopDemo()") 22 public void around2(ProceedingJoinPoint pjp) throws Throwable{ 23 System.out.println("[Aspect2] around advise 12"); 24 pjp.proceed(); 25 System.out.println("[Aspect2] around advise22"); 26 } 27 28 @AfterReturning(value = "test.test.PointCuts.aopDemo()") 29 public void afterReturning(JoinPoint joinPoint) { 30 System.out.println("[Aspect2] afterReturning advise"); 31 } 32 33 @AfterThrowing(value = "test.test.PointCuts.aopDemo()") 34 public void afterThrowing(JoinPoint joinPoint) { 35 System.out.println("[Aspect2] afterThrowing advise"); 36 } 37 38 @After(value = "test.test.PointCuts.aopDemo()") 39 public void after(JoinPoint joinPoint) { 40 System.out.println("[Aspect2] after advise"); 41 } 42 }
你知道改切面里的这些通知的执行顺序么?
如果有多个切面呢,那这些通知执行顺序又是怎么样的?
如果在spring事务控制的service上新增aop切面,其执行顺序如何?
注:如果看不懂这些代码,请看本文最后spring aop知识点
二.模拟切面执行过程
实际AOP通知执行顺序代码太多了,不合适贴到博客里,所以使用模拟代码来说明执行过程;实际AOP测试代码,大家可以自己测试一下,多搞几个切面,都切入到同一个连接点。
以下代码,模拟了多切面的执行过程(正常流程,不抛异常)

1 package test.test; 2 3 4 public class AopBean { 5 6 private AopBean next; 7 private int n;//切面中@Around通知数量 8 private String aspectName;//切面名称 9 private int m = 2;//定义@Before、@After、@AfterReturing、@AfterThrowing的数量,为了展示执行顺序效果 10 public static void main(String[] args) throws Exception { 11 AopBean bean1 = new AopBean(null,1,"aspect1"); 12 AopBean bean2 = new AopBean(bean1,1,"aspect2"); 13 AopBean bean3 = new AopBean(bean2,2,"aspect3"); 14 bean3.aspectInvoke(); 15 } 16 17 AopBean(AopBean next,int n,String aspectName){ 18 this.next = next; 19 this.n = n; 20 this.aspectName = aspectName; 21 } 22 /** 23 * 切面执行过程 24 * @param n 该切面总共n层环绕增强 25 * @throws Exception 26 */ 27 public void aspectInvoke() throws Exception{ 28 try { 29 try { 30 aroundAdvice(n); 31 } finally { 32 afterAdvice(); 33 } 34 afterReturningAdvice(); 35 return; 36 } catch (Exception e) { 37 afterThrowingAdvice(e); 38 throw e; 39 } 40 } 41 42 /** 43 * 环绕增强,嵌套调用 44 * @param n 第几层环绕增强 45 * @throws Exception 46 */ 47 private void aroundAdvice(int n) throws Exception { 48 if(n==0) { 49 JoinPoint_Proceed(); 50 return; 51 } 52 System.out.println(aspectName+ " around before advice-"+n); 53 aroundAdvice(n-1); 54 System.out.println(aspectName + " around after advice-"+n); 55 } 56 57 /** 58 * 编织后的接入点执行过程 59 * @throws Exception 60 */ 61 public void JoinPoint_Proceed() throws Exception{ 62 beforeAdvice(); 63 targetMethod(); 64 } 65 66 /** 67 * 前置增强 68 */ 69 private void beforeAdvice() { 70 //如果定义多个@Before,都会在这里按顺序调用(顺序未知) 71 for(int i = 0;i<m;++i) { 72 System.out.println(aspectName +" before advice " + (i+1)); 73 } 74 75 } 76 77 /** 78 * 目标方法 79 * @param isException 80 * @throws Exception 81 */ 82 private void targetMethod() throws Exception { 83 if(next == null) { 84 System.out.println("连接点 method 执行"); 85 return; 86 //throw new RuntimeException("为测试抛的异常"); 87 } 88 //执行下一个切面 89 next.aspectInvoke(); 90 91 } 92 93 /** 94 * 后置增强 95 */ 96 private void afterAdvice() { 97 //如果定义多个@After,都会在这里按顺序调用(顺序未知) 98 for(int i=0;i<m;++i) 99 System.out.println(aspectName+" after advice " + (i+1)); 100 } 101 102 /** 103 * 正常返回增强 104 */ 105 private void afterReturningAdvice() { 106 //如果定义多个@AfterReturning,都会在这里按顺序调用(顺序未知) 107 for(int i=0;i<m;++i) 108 System.out.println(aspectName + " afterReturning " + (i+1) ); 109 } 110 111 /** 112 * 异常返回增强 113 * @param e 114 * @throws Exception 115 */ 116 private void afterThrowingAdvice(Exception e) throws Exception { 117 //如果定义多个@AfterThrowing,都会在这里按顺序调用(顺序未知) 118 for(int i=0;i<m;++i) 119 System.out.println(aspectName + " afterThrowing: "+ (i+1) +e.getMessage()); 120 } 121 122 }
代码执行结果如下:
aspect3 around before advice-2 aspect3 around before advice-1 aspect3 before advice 1 aspect3 before advice 2 aspect2 around before advice-1 aspect2 before advice 1 aspect2 before advice 2 aspect1 around before advice-1 aspect1 before advice 1 aspect1 before advice 2 连接点 method 执行 aspect1 around after advice-1 aspect1 after advice 1 aspect1 after advice 2 aspect1 afterReturning 1 aspect1 afterReturning 2 aspect2 around after advice-1 aspect2 after advice 1 aspect2 after advice 2 aspect2 afterReturning 1 aspect2 afterReturning 2 aspect3 around after advice-1 aspect3 around after advice-2 aspect3 after advice 1 aspect3 after advice 2 aspect3 afterReturning 1 aspect3 afterReturning 2
如果把targetMethod中86行放开,让执行连接点时抛异常的话,执行结果如下:
aspect3 around before advice-2 aspect3 around before advice-1 aspect3 before advice 1 aspect3 before advice 2 aspect2 around before advice-1 aspect2 before advice 1 aspect2 before advice 2 aspect1 around before advice-1 aspect1 before advice 1 aspect1 before advice 2 连接点 method 执行 aspect1 after advice 1 aspect1 after advice 2 aspect1 afterThrowing: 1为测试抛的异常 aspect1 afterThrowing: 2为测试抛的异常 aspect2 after advice 1 aspect2 after advice 2 aspect2 afterThrowing: 1为测试抛的异常 aspect2 afterThrowing: 2为测试抛的异常 aspect3 after advice 1 aspect3 after advice 2 aspect3 afterThrowing: 1为测试抛的异常 aspect3 afterThrowing: 2为测试抛的异常 Exception in thread "main" java.lang.RuntimeException: 为测试抛的异常 at test.test.AopBean.targetMethod(AopBean.java:86) at test.test.AopBean.JoinPoint_Proceed(AopBean.java:63) at test.test.AopBean.aroundAdvice(AopBean.java:49) at test.test.AopBean.aroundAdvice(AopBean.java:53) at test.test.AopBean.aspectInvoke(AopBean.java:30) at test.test.AopBean.targetMethod(AopBean.java:89) at test.test.AopBean.JoinPoint_Proceed(AopBean.java:63) at test.test.AopBean.aroundAdvice(AopBean.java:49) at test.test.AopBean.aroundAdvice(AopBean.java:53) at test.test.AopBean.aspectInvoke(AopBean.java:30) at test.test.AopBean.targetMethod(AopBean.java:89) at test.test.AopBean.JoinPoint_Proceed(AopBean.java:63) at test.test.AopBean.aroundAdvice(AopBean.java:49) at test.test.AopBean.aroundAdvice(AopBean.java:53) at test.test.AopBean.aroundAdvice(AopBean.java:53) at test.test.AopBean.aspectInvoke(AopBean.java:30) at test.test.AopBean.main(AopBean.java:14)
注意:如果执行连接点方法时抛了异常,@Around after 就不会被执行了
三.结论
1.多个切面的执行顺序是随机的,如果要指定顺序,请用注解@Order(n),n越小越先执行 2.单个切面里同类型通知(比如多个@Before),其执行顺序无法明确指定 入操作(Around(接入点执行前)、Before),优先级越高,越先执行; 3.一个切面的入操作执行完,才轮到下一切面,所有切面入操作执行完,才开始执行接入点; 4.出操作(Around(接入点执行后)、After、AfterReturning、AfterThrowing),优先级越低,越先执行。 5.一个切面的出操作执行完,才轮到下一切面,直到返回到调用点; 6.切面之间,先入后出,后入先出 7.同一切面中的@Around之间,也是先入后出,后入先出 8.Spring事务管理(Transaction Management),也是基于Spring AOP;事务切面(Transaction Aspect)优先级最低,就是@Order(Integer.MAX_VALUE),其最后被启动,最早结束;事务的增强类型是@Around类型。
如下图所示:
四.spring aop部分知识点
1.Aspect:切面,由一系列切点、增强和引入组成的模块对象,可定义优先级,从而影响增强和引入的执行顺序。事务管理(Transaction management)在java企业应用中就是一个很好的切面样例。对应注解 @Aspect 2.Join point:接入点,程序执行期的一个点,例如方法执行、类初始化、异常处理。 在Spring AOP中,接入点始终表示方法执行;这是个被切入的对象,即使没有AOP,他也是客观存在的。无AOP注解 3.Advice:增强(通知),切面在特定接入点的执行动作,包括 “around,” “before” and "after"等多种类型。包含Spring在内的许多AOP框架,通常会使用拦截器来实现增强,围绕着接入点维护着一个拦截器链。对应注解@Before、@Around、@After、@AfterReturning、@AfterThrowing 4.Pointcut:切点,用来匹配特定接入点的谓词(表达式),增强将会与切点表达式产生关联,并运行在任何切点匹配到的接入点上。通过切点表达式匹配接入点是AOP的核心,Spring默认使用AspectJ的切点表达式。对应注解@Pointcut(value = "within(test.test.*)") 5.Introduction:引入,为某个type声明额外的方法和字段。Spring AOP允许你引入任何接口以及它的默认实现到被增强对象上。 6.Target object:目标对象,被一个或多个切面增强的对象。也叫作被增强对象。既然Spring AOP使用运行时代理(runtime proxies),那么目标对象就总是代理对象。 7.AOP proxy:AOP代理,为了实现切面功能一个对象会被AOP框架创建出来。在Spring框架中AOP代理的默认方式是:有接口,就使用基于接口的JDK动态代理,否则使用基于类的CGLIB动态代理。但是我们可以通过设置proxy-target-class="true",完全使用CGLIB动态代理。 8.Weaving:织入,将一个或多个切面与类或对象链接在一起创建一个被增强对象。织入能发生在编译时 (compile time )(使用AspectJ编译器),加载时(load time),或运行时(runtime) 。Spring AOP默认就是运行时织入,可以通过枚举AdviceMode来设置。
本文参考了 https://blog.csdn.net/qq_32331073/article/details/80596084,使用文中部分内容,如有侵权,请联系我
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构