Spring Aop實例@Aspect、@Before、@AfterReturning@Around 注解方式配置


總結:

理解AOP@Before,@After,@AfterReturning,@AfterThrowing執行順序 

實現AOP的切面主要有以下幾個要素:

使用@Aspect注解將一個java類定義為切面類

使用@Pointcut定義一個切入點,可以是一個規則表達式,比如下例中某個package下的所有函數,也可以是一個注解等。

根據需要在切入點不同位置的切入內容

使用@Before在切入點開始處切入內容

使用@After在切入點結尾處切入內容

使用@AfterReturning在切入點return內容之后切入內容(可以用來對處理返回值做一些加工處理)

使用@Around在切入點前后切入內容,並自己控制何時執行切入點自身的內容

使用@AfterThrowing用來處理當切入內容部分拋出異常之后的處理邏輯

try{  
     try{  
         doBefore();//對應@Before注解的方法切面邏輯  
         method.invoke();  
     }finally{  
         doAfter();//對應@After注解的方法切面邏輯  
     }  
     doAfterReturning();//對應@AfterReturning注解的方法切面邏輯  
 }catch(Exception e){  
      doAfterThrowing();//對應@AfterThrowing注解的方法切面邏輯  
 }  

 

用過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() {
 
} 
}

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();給刪掉,那么,我們看到的輸出結果將是:從結果可以發現, Controller 中的 接口 未被執行,aspect1 中的 @Before advice 也未被執行。

  • [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

     

參考:Spring Aop實例@Aspect、@Before、@AfterReturning@Around 注解方式配置


免責聲明!

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



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