Spring Boot使用AOP的正確姿勢

James_Shangguan發表於2020-07-22

一、為什麼需要面向切面程式設計?

物件導向程式設計(OOP)的好處是顯而易見的,缺點也同樣明顯。當需要為多個不具有繼承關係的物件新增一個公共的方法的時候,例如日誌記錄、效能監控等,如果採用物件導向程式設計的方法,需要在每個物件裡面都新增相同的方法,這樣就產生了較大的重複工作量和大量的重複程式碼,不利於維護。面向切面程式設計(AOP)是物件導向程式設計的補充,簡單來說就是統一處理某一“切面”的問題的程式設計思想。如果使用AOP的方式進行日誌的記錄和處理,所有的日誌程式碼都集中於一處,不需要再每個方法裡面都去新增,極大減少了重複程式碼。

二、Spring AOP術語

Spring AOP術語

通知(Advice)包含了需要用於多個應用物件的橫切行為,完全聽不懂,沒關係,通俗一點說就是定義了“什麼時候”和“做什麼”。

連線點(Join Point)是程式執行過程中能夠應用通知的所有點。

切點(Poincut)是定義了在“什麼地方”進行切入,哪些連線點會得到通知。顯然,切點一定是連線點。

切面(Aspect)是通知和切點的結合。通知和切點共同定義了切面的全部內容——是什麼,何時,何地完成功能。

引入(Introduction)允許我們向現有的類中新增新方法或者屬性。

織入(Weaving)是把切面應用到目標物件並建立新的代理物件的過程,分為編譯期織入、類載入期織入和執行期織入。

三、Spring Boot AOP實戰

3.1 引入依賴

Spring Boot使用AOP需要新增spring-boot-starter-aop依賴,如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

不需要再新增aspectjweaver的依賴了,因為spring-boot-starter-aop包含了aspectjweaver,並且版本是較新的版本,如果在新增老版本(如1.5.4)啟動會報錯。
Maven依賴

3.2 編寫用於攔截的bean

直接定義一個controller,程式碼如下:

@RestController
public class AopController {

    @RequestMapping("/hello")
    public String sayHello(){
        System.out.println("hello");
        return "hello";
    }
}

3.3 定義切面

Spring採用@AspectJ註解對POJO進行標註,該註解表明該類不僅僅是一個POJO,還是一個切面。切面是切點和通知的結合,那麼定義一個切面就需要編寫切點和通知。在程式碼中,只需要新增@Aspect註解即可。

3.3.1 定義切點

切點是通過@Pointcut註解和切點表示式定義的。

@Pointcut註解可以在一個切面內定義可重用的切點。

由於Spring切面粒度最小是達到方法級別,而execution表示式可以用於明確指定方法返回型別,類名,方法名和引數名等與方法相關的部件,並且實際中,大部分需要使用AOP的業務場景也只需要達到方法級別即可,因而execution表示式的使用是最為廣泛的。如圖是execution表示式的語法:

execution表示在方法執行的時候觸發。以“”開頭,表明方法返回值型別為任意型別。然後是全限定的類名和方法名,“”可以表示任意類和任意方法。對於方法引數列表,可以使用“..”表示引數為任意型別。如果需要多個表示式,可以使用“&&”、“||”和“!”完成與、或、非的操作。

切點表示式

3.3.2 定義通知

通知有五種型別,分別是:

前置通知(@Before):在目標方法呼叫之前呼叫通知

後置通知(@After):在目標方法完成之後呼叫通知

環繞通知(@Around):在被通知的方法呼叫之前和呼叫之後執行自定義的方法

返回通知(@AfterReturning):在目標方法成功執行之後呼叫通知

異常通知(@AfterThrowing):在目標方法丟擲異常之後呼叫通知

程式碼中定義了三種型別的通知,使用@Before註解標識前置通知,列印“beforeAdvice...”,使用@After註解標識後置通知,列印“AfterAdvice...”,使用@Around註解標識環繞通知,在方法執行前和執行之後分別列印“before”和“after”。這樣一個切面就定義好了,程式碼如下:

@Aspect
@Component
public class AopAdvice {

    @Pointcut("execution (* com.shangguan.aop.controller.*.*(..))")
    public void test() {

    }

    @Before("test()")
    public void beforeAdvice() {
        System.out.println("beforeAdvice...");
    }

    @After("test()")
    public void afterAdvice() {
        System.out.println("afterAdvice...");
    }

    @Around("test()")
    public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
        System.out.println("before");
        try {
            proceedingJoinPoint.proceed();
        } catch (Throwable t) {
            t.printStackTrace();
        }
        System.out.println("after");
    }

}

3.4 啟動測試

完成之後的程式碼結構如圖所示:

程式碼結構

執行AopApplication,在瀏覽器訪問http://localhost:8080/hello,不出意外,控制檯輸出如圖所示:

控制檯輸出結果

相關文章