@Before、@Around和@After執行順序

轉載:http://blog.csdn.net/rainbow702/article/details/52185827css

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

前提

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

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

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

添加 PointCut

該pointcut用來攔截test包下的全部類中的全部方法。web

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

添加Aspect

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

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,回車:ruby

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

測試 異常狀況

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

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 也未被執行。

參考資料

Advice ordering

What happens when multiple pieces of advice all want to run at the same join point? Spring AOP follows the same precedence rules as AspectJ to determine the order of advice execution. The highest precedence advice runs first "on the way in" (so given two pieces of before advice, the one with highest precedence runs first). "On the way out" from a join point, the highest precedence advice runs last (so given two pieces of after advice, the one with the highest precedence will run second). When two pieces of advice defined in different aspects both need to run at the same join point, unless you specify otherwise the order of execution is undefined. You can control the order of execution by specifying precedence. This is done in the normal Spring way by either implementing the org.springframework.core.Ordered interface in the aspect class or annotating it with the Order annotation. Given two aspects, the aspect returning the lower value from Ordered.getValue() (or the annotation value) has the higher precedence. When two pieces of advice defined in the same aspect both need to run at the same join point, the ordering is undefined (since there is no way to retrieve the declaration order via reflection for javac-compiled classes). Consider collapsing such advice methods into one advice method per join point in each aspect class, or refactor the pieces of advice into separate aspect classes - which can
相關文章
相關標籤/搜索