1. 程式人生 > >Spring Aop例項@Around、@Before、@After、@AfterReturning 、@AfterThrowing註解方式配置

Spring Aop例項@Around、@Before、@After、@AfterReturning 、@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() {
 
    }
}

新增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://localhost:9999/aop/test?throwException=false

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

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

測試 異常情況

http://localhost:9999/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://localhost:9999/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://localhost:9999/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 也未被執行。