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 }
View Code

 

    你知道改切面里的这些通知的执行顺序么?

    如果有多个切面呢,那这些通知执行顺序又是怎么样的?

    如果在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 }
View Code

   代码执行结果如下:

   

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,使用文中部分内容,如有侵权,请联系我

 

posted @ 2022-02-11 16:55  高压锅里的大萝卜  阅读(577)  评论(0编辑  收藏  举报