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 將按照以下的順序進行執行:
正常情況:
異常情況:
情況二: 同一個方法被多個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 先執行。如下圖所示:
註意點
-
如果在同一個 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
Spring AOP @Before @Around @After 等 advice 的執行順序