Spring AOP @Before @Around @After 等 advice 的執行順序


用過spring框架進行開發的人,多多少少會使用過它的AOP功能,都知道有@Before@Around@After等advice。最近,為了實現項目中的輸出日志權限控制這兩個需求,我也使用到了AOP功能。我使用到了@Before@Around這兩個advice。但在,使用過程中,卻對它們的執行順序並不清楚。為了弄清楚在不同情況下,這些advice到底是以怎么樣的一個順序進行執行的,我作了個測試,在此將其記錄下來,以供以后查看。

前提

  • 對於AOP相關類(aspect、pointcut等)的概念,本文不作說明。
  • 對於如何讓spring框架掃描到AOP,本文也不作說明。

情況一: 一個方法只被一個Aspect類攔截

當一個方法只被一個Aspect攔截時,這個Aspect中的不同advice是按照怎樣的順序進行執行的呢?請看:

添加 PointCut

該pointcut用來攔截test包下的所有類中的所有方法。

package test; import org.aspectj.lang.annotation.Pointcut; public class PointCuts { @Pointcut(value = "within(test.*)") public void aopDemo() { } }

添加Aspect

該類中的advice將會用到上面的pointcut,使用方法請看各個advice的value屬性。

package test; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Component @Aspect public class Aspect1 { @Before(value = "test.PointCuts.aopDemo()") public void before(JoinPoint joinPoint) { System.out.println("[Aspect1] before advise"); } @Around(value = "test.PointCuts.aopDemo()") public void around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("[Aspect1] around advise 1"); pjp.proceed(); System.out.println("[Aspect1] around advise2"); } @AfterReturning(value = "test.PointCuts.aopDemo()") public void afterReturning(JoinPoint joinPoint) { System.out.println("[Aspect1] afterReturning advise"); } @AfterThrowing(value = "test.PointCuts.aopDemo()") public void afterThrowing(JoinPoint joinPoint) { System.out.println("[Aspect1] afterThrowing advise"); } @After(value = "test.PointCuts.aopDemo()") public void after(JoinPoint joinPoint) { System.out.println("[Aspect1] after advise"); } }

添加測試用Controller

添加一個用於測試的controller,這個controller中只有一個方法,但是它會根據參數值的不同,會作出不同的處理:一種是正常返回一個對象,一種是拋出異常(因為我們要測試@AfterThrowing這個advice)

package test; import test.exception.TestException; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping(value = "/aop") public class AopTestController { @ResponseStatus(HttpStatus.OK) @RequestMapping(value = "/test", method = RequestMethod.GET) public Result test(@RequestParam boolean throwException) { // case 1 if (throwException) { System.out.println("throw an exception"); throw new TestException("mock a server exception"); } // case 2 System.out.println("test OK"); return new Result() {{ this.setId(111); this.setName("mock a Result"); }}; } public static class Result { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } }

測試 正常情況

在瀏覽器直接輸入以下的URL,回車:

http://192.168.142.8:7070/aoptest/v1/aop/test?throwException=false

我們會看到輸出的結果是:

[Aspect1] around advise 1 [Aspect1] before advise test OK [Aspect1] around advise2 [Aspect1] after advise [Aspect1] afterReturning advise

測試 異常情況

在瀏覽器中直接輸入以下的URL,回車:

http://192.168.142.8:7070/aoptest/v1/aop/test?throwException=true

我們會看到輸出的結果是:

[Aspect1] around advise 1 [Aspect1] before advise throw an exception [Aspect1] after advise [Aspect1] afterThrowing advise

結論

在一個方法只被一個aspect類攔截時,aspect類內部的 advice 將按照以下的順序進行執行:

正常情況: 
one-ok


異常情況: 
one-exception

情況二: 同一個方法被多個Aspect類攔截

此處舉例為被兩個aspect類攔截。 
有些情況下,對於兩個不同的aspect類,不管它們的advice使用的是同一個pointcut,還是不同的pointcut,都有可能導致同一個方法被多個aspect類攔截。那么,在這種情況下,這多個Aspect類中的advice又是按照怎樣的順序進行執行的呢?請看:

pointcut類保持不變

添加一個新的aspect類

package test; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Component @Aspect public class Aspect2 { @Before(value = "test.PointCuts.aopDemo()") public void before(JoinPoint joinPoint) { System.out.println("[Aspect2] before advise"); } @Around(value = "test.PointCuts.aopDemo()") public void around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("[Aspect2] around advise 1"); pjp.proceed(); System.out.println("[Aspect2] around advise2"); } @AfterReturning(value = "test.PointCuts.aopDemo()") public void afterReturning(JoinPoint joinPoint) { System.out.println("[Aspect2] afterReturning advise"); } @AfterThrowing(value = "test.PointCuts.aopDemo()") public void afterThrowing(JoinPoint joinPoint) { System.out.println("[Aspect2] afterThrowing advise"); } @After(value = "test.PointCuts.aopDemo()") public void after(JoinPoint joinPoint) { System.out.println("[Aspect2] after advise"); } }

測試用Controller也不變

還是使用上面的那個Controller。但是現在 aspect1 和 aspect2 都會攔截該controller中的方法。

下面繼續進行測試!

測試 正常情況

在瀏覽器直接輸入以下的URL,回車:

http://192.168.142.8:7070/aoptest/v1/aop/test?throwException=false

我們會看到輸出的結果是:

[Aspect2] around advise 1 [Aspect2] before advise [Aspect1] around advise 1 [Aspect1] before advise test OK [Aspect1] around advise2 [Aspect1] after advise [Aspect1] afterReturning advise [Aspect2] around advise2 [Aspect2] after advise [Aspect2] afterReturning advise

但是這個時候,我不能下定論說 aspect2 肯定就比 aspect1 先執行。 
不信?你把服務務器重新啟動一下,再試試,說不定你就會看到如下的執行結果:

[Aspect1] around advise 1 [Aspect1] before advise [Aspect2] around advise 1 [Aspect2] before advise test OK [Aspect2] around advise2 [Aspect2] after advise [Aspect2] afterReturning advise [Aspect1] around advise2 [Aspect1] after advise [Aspect1] afterReturning advise

也就是說,這種情況下, aspect1 和 aspect2 的執行順序是未知的。那怎么解決呢?不急,下面會給出解決方案。

測試 異常情況

在瀏覽器中直接輸入以下的URL,回車:

http://192.168.142.8:7070/aoptest/v1/aop/test?throwException=true

我們會看到輸出的結果是:

[Aspect2] around advise 1 [Aspect2] before advise [Aspect1] around advise 1 [Aspect1] before advise throw an exception [Aspect1] after advise [Aspect1] afterThrowing advise [Aspect2] after advise [Aspect2] afterThrowing advise

同樣地,如果把服務器重啟,然后再測試的話,就可能會看到如下的結果:

[Aspect1] around advise 1 [Aspect1] before advise [Aspect2] around advise 1 [Aspect2] before advise throw an exception [Aspect2] after advise [Aspect2] afterThrowing advise [Aspect1] after advise [Aspect1] afterThrowing advise

也就是說,同樣地,異常情況下, aspect1 和 aspect2 的執行順序也是未定的。

那么在 情況二 下,如何指定每個 aspect 的執行順序呢? 
方法有兩種:

  • 實現org.springframework.core.Ordered接口,實現它的getOrder()方法
  • 給aspect添加@Order注解,該注解全稱為:org.springframework.core.annotation.Order

不管采用上面的哪種方法,都是值越小的 aspect 越先執行。 
比如,我們為 apsect1 和 aspect2 分別添加 @Order 注解,如下:

@Order(5) @Component @Aspect public class Aspect1 { // ... } @Order(6) @Component @Aspect public class Aspect2 { // ... }

這樣修改之后,可保證不管在任何情況下, aspect1 中的 advice 總是比 aspect2 中的 advice 先執行。如下圖所示: 
two-ok

注意點

  • 如果在同一個 aspect 類中,針對同一個 pointcut,定義了兩個相同的 advice(比如,定義了兩個 @Before),那么這兩個 advice 的執行順序是無法確定的,哪怕你給這兩個 advice 添加了 @Order 這個注解,也不行。這點切記。

  • 對於@Around這個advice,不管它有沒有返回值,但是必須要方法內部,調用一下 pjp.proceed();否則,Controller 中的接口將沒有機會被執行,從而也導致了 @Before這個advice不會被觸發。比如,我們假設正常情況下,執行順序為”aspect2 -> apsect1 -> controller”,如果,我們把 aspect1中的@Around中的 pjp.proceed();給刪掉,那么,我們看到的輸出結果將是:

[Aspect2] around advise 1 [Aspect2] before advise [Aspect1] around advise 1 [Aspect1] around advise2 [Aspect1] after advise [Aspect1] afterReturning advise [Aspect2] around advise2 [Aspect2] after advise [Aspect2] afterReturning advise

從結果可以發現, Controller 中的 接口 未被執行,aspect1 中的 @Before advice 也未被執行。

 

來源:http://blog.csdn.net/rainbow702/article/details/52185827


免責聲明!

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



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