AOP (Aspect Oriented Programming)是Spring中一種重要的編程思想。AOP涉及Aspect、Advice、Join point、Pointcut、Weaving、Target等多個概念,實現依賴於Spring中的@Aspect、@Pointcut、@Before、@... ...
零、準備知識
1)AOP相關概念:Aspect、Advice、Join point、Pointcut、Weaving、Target等。 ref: https://www.cnblogs.com/zhangzongle/p/5944906.html 有代碼示例 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.cnblogs.com/liaojie970/p/7883687.html 常用表達式 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的插入時機,待讀源碼學習。