Spring入門之AOP實踐:@Aspect + @Pointcut + @Before / @Around / @After


零、准備知識

1)AOP相關概念:Aspect、Advice、Join point、Pointcut、Weaving、Target等。
2)相關注解:@Aspect、@Pointcut、@Before、@Around、@After、@AfterReturning、@AfterThrowing
 

一、實踐目標

1)@Aspect的功能
2)@Pointcut的切面表達式
3)@Before、@Around、@After、@AfterReturning / @AfterThrowing的時序關系
4)AOP概念的重新梳理和使用
 

二、核心代碼

MainController.java包含兩個測試函數,分別是doSomething() 和 test()。
 1 // MainController.java
 2 @RestController
 3 public class MainController {
 4     RequestMapping(value="/doSomething", method = RequestMethod.POST)
 5     @CrossOrigin("*")
 6     public void doSomething() { 
 7         System.out.println("This is doSomething"); 
 8         test();
 9     }
10 
11     @RequestMapping(value="/justTest", method = RequestMethod.POST)
12     @CrossOrigin("*")
13     public void test() { System.out.println("This is test");}
14 }

 

ExampleAop.java為AOP相關代碼,定義了pointcut和多個Advice函數。

 1 // ExampleAop.java
 2 @Component
 3 @Aspect
 4 @Order(1)
 5 public class ExampleAop {
 6 
 7     private static final Logger LOGGER = LoggerFactory.getLogger(ExampleAop.class);
 8 
 9     // 匹配com.example.demo包及其子包下的所有類的所有方法
10     @Pointcut("execution(* com.example.demo..*.*(..))")
11     public void executeService() {
12     }
13 
14     @Before("executeService()")
15     public void doBeforeAdvice(JoinPoint joinPoint) throws Exception {
16         LOGGER.info("Before [{}]", joinPoint.getSignature().getName());
17     }
18 
19     @Around("executeService()")
20     public void doAroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
21         LOGGER.info("Around1 [{}]", joinPoint.getSignature().getName());
22         joinPoint.proceed();
23         LOGGER.info("Around2 [{}]", joinPoint.getSignature().getName());
24     }
25 
26     @After("executeService()")
27     public void doAfterAdvice(JoinPoint joinPoint) throws Exception {
28         LOGGER.info("After [{}]", joinPoint.getSignature().getName());
29     }
30 
31     @AfterReturning("executeService()")
32     public void doAfterReturningAdvice(JoinPoint joinPoint) throws Exception {
33         LOGGER.info("AfterReturning [{}]", joinPoint.getSignature().getName());
34     }
35 }

 

編譯並運行jar包,調用接口/doSomething。

1 # 調用接口
2 curl localhost:8080/doSomething

 

到服務器觀察日志。

1 <!-- 后台日志 -->
2 com.example.demo.aop.ExampleAop          : Around1 [doSomething]
3 com.example.demo.aop.ExampleAop          : Before [doSomething]
4 This is doSomething
5 This is test
6 com.example.demo.aop.ExampleAop          : Around2 [doSomething]
7 com.example.demo.aop.ExampleAop          : After [doSomething]
8 com.example.demo.aop.ExampleAop          : AfterReturning [doSomething]

 

三、分析與結論

1)@Aspect的功能
  在連接點的前后添加處理。在本例中,doSomething() 是連接點,而test() 不是。
  是否只有最外層的joinpoint才會被Advice插入?在后面進行簡單的探討和猜測。
 
 
2)@Pointcut的切面表達式
  ref: https://www.jianshu.com/p/fbbdebf200c9  完整表達式
  @Pointcut("execution(...)") 是Pointcut表達式,executeService() 是point簽名。表達式中可以包含簽名的邏輯運算。
 
  常用表達式:
1 execution(public * com.example.demo.ExampleClass.*(..))  // ExampleClass的所有公有方法
2 execution(* com.example.demo..*.*(..)) // com.example.demo包及其子包下的所有方法
3 logSender() || logMessage() // 兩個簽名的表達式的並集

 

 

3)@Before、@Around、@After、@AfterReturning / @AfterThrowing的時序關系
  @Around1 -> @Before -> 方法 -> @Around2 -> @After -> @AfterReturning / @AfterThrowing(時序問題后面有額外討論。)
  另外可以發現,@Around是可以影響程序本身執行的,如果不調用 joinPoint.proceed(); 就不會執行方法。其他幾個都無法影響程序執行。
 
 
4)AOP概念的重新梳理和使用
   Aspect(切面):使用了@Aspect注解,如ExampleAop類。
   Advice(增強):在指定位置進行的增強操作,如方法運行時間統計、用戶登錄、日志記錄等。由@Before、@After等注解標注,如doBeforeAdvice() 方法。
   Weaving(織入):AOP就是一種把Advice織入(即嵌入、插入)到Aspect中指定位置執行的機制。
   Join point(連接點):Advice執行的位置,也是Advice的參數,是一個具體的方法。如日志中看到的doSomething() 函數。
   Pointcut(切點):以表達式的形式表示一組join point,用於由@Pointcut注解定義Advice的作用位置。如@Pointcut("execution(* com.example.demo..*.*(..))") 代表com.example.demo包及其子包下的所有類的所有方法。
   Target(對象):被增強的對象,即包含主業務邏輯的類的對象,如ExampleAop類的實例。

 

四、疑問與討論

1. 本文說執行順序為@Around1 -> @Before -> 方法 -> @Around2 -> @After,但有的文章中說是@Before -> @Around1 -> 方法 -> @Around2 -> @After,也有說@Around1 -> @Before -> 方法 -> @After -> @Around2,哪個對?
 
  反正代碼跑起來是這個順序,那就是這個順序嘍。每個Advice都加上sleep拉開時間也沒有變化。不知道是否受版本或代碼自身影響。
  總之可以得到一個結論:@Before / @After 最好不要和@Around混用,執行順序不好確定。
  時序至少總是滿足:@Around1 / @Before -> 方法 -> @Around2 / @After -> @AfterReturning / @AfterThrowing
  另外找到一篇支持本文執行順序的文章:https://blog.csdn.net/qq_32331073/article/details/80596084
 
 
2. 為何doSomething() 和 test() 都是@Pointcut中選中的作用節點,但只有doSomething() 插入了Advice,而test() 沒有呢?
 
        一個猜測:從字面意思理解,@Pointcut對所有代碼以表達式為規則剪一刀,一側是所有的joinpoint,另一側是普通代碼。在joinpoint與另一側代碼間插入一層Advice的代理,另一側的代碼如果要調用joinpoint,則必須經Advice進行增強操作。而不同的joinpoint在同一側,因此未插入Advice。
        有時間再讀源碼了解其中的機制。

(一個猜測)

 

五、Future Work

1. AOP中還有Advisor等概念,待學習。
2. 切面表達式(@Pointcut里的表達式)規則豐富,待學習。
3. Advice的插入時機,待讀源碼學習。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM