Day 12 - Spring AOP 的用法 - @Aspect

古古

2024/07/12


哈囉大家好,我是古古。

在上一篇文章中,我們有去介紹了 Spring AOP 的概念和原理,讓大家先對 Spring AOP 有一個初步的認識。

那麼這篇文章,我們就會接著來介紹,要如何在 Spring Boot 中使用 Spring AOP 的功能。

補充:目前在實務上,其實已經不太會直接實作 Spring AOP 的程式了,所以本章節的內容大家就有個印象就好,等到將來真的有需要實作 Spring AOP 時,再回來查看本章節的 @Aspect 用法即可。

回顧:什麼是 Spring AOP? #

在上一篇文章中有提到,AOP 的全稱是 Aspect-Oriented Programming,中文翻譯成「切面導向程式設計」或是「剖面導向程式設計」,而 AOP 的概念,就是「透過切面,統一的去處理方法之間的共同邏輯」。

因此當我們使用了 AOP 之後,就再也不用去複製貼上程式了,我們只需要在切面裡面寫好測量時間的程式,之後就可以在任何地方去使用這個切面,讓這個切面替我們完成測量時間的功能了。

在 pom.xml 載入 Spring AOP 的功能 #

如果想要在 Spring Boot 中使用 Spring AOP 的功能的話,首先會需要在 pom.xml 檔案中新增下列的程式,這樣才能將 Spring AOP 的功能給載入進來,後續我們才能夠在 Spring Boot 中使用 Spring AOP 所提供的註解。

所以大家可以先打開左邊側邊欄中的 pom.xml 檔案,然後在第 25 行~第 28 行處,添加下面的程式:

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

添加好上述的程式之後,此時在 pom.xml 的右上角會出現一個 M 符號,這時記得要點擊一下 M 符號,才能夠成功更新這個 Spring Boot 程式,把 Spring AOP 的功能給載入進來。

載入好 Spring AOP 的功能之後,接下來我們就可以在 Spring Boot 中使用 Spring AOP 專屬的註解,去實作一個 AOP 的切面出來了!

創建切面的方法:@Aspect #

如果想要使用 Spring AOP 去創造一個新的切面出來的話,我們就只要在 class 上面,去加上一個 @Aspect 的註解,這樣子就可以成功創建一個切面出來了。

譬如說我們可以先創建一個新的 class 叫做 MyAspect,然後在上面加上 @Aspect,這樣就可以將 MyAspect 變成是一個切面了。

不過在使用 @Aspect 去創建新切面時,有一點一定要特別注意,就是「只有 Bean 才可以變成一個切面」。

所以換句話說的話,在使用 @Aspect 去創建一個新的切面時,同時也必須要使用 @Component,將這個 class 變成是一個 Bean,這樣子 @Aspect 的切面設定才會真的生效!!如果單純只有在 class 上面加上 @Aspect 的話,是完全沒有任何效果的!

所以大家在實作時一定要記得,在創建切面時,「@Component@Aspect 要一起使用」 就對了。

在切入點方法「執行前」執行切面:@Before #

創建好切面 MyAspect 這個切面 class 之後,我們就可以在這個 class 裡面,去撰寫切面的方法了。

舉例來說,我們可以在 MyAspect 裡面,先寫上一個 before() 方法,然後在這個 before() 方法裡面,輸出一行「I’m before」的訊息到 console 上。

接著,只要我們在這個 before() 方法上面,去加上一個 @Before 註解,並且在後面的小括號中,去指定想要的切入點,這樣子就可以在這個切入點的方法 「執行前」,去執行這個 MyAspect 中的 before() 方法了。

不過看到這裡,大家可能還是會對 @Aspect@Before 的用法有點疑惑(畢竟真的有點抽象),所以接下來我們可以再試著來拆解一下這段程式,了解要如何解讀這些 AOP 的程式。

如何解讀 AOP 程式? #

到目前為止,我們已經有在 MyAspect 中,先寫上了一個 before() 方法,並且在這個 before() 方法的上面,也有去加上了一個 @Before 的註解,完成這個切面的實作。

不過其實上面這一段程式,他是可以拆成三個步驟來解讀的:

步驟一:先閱讀 @Before 小括號中的程式 #

@Before 後面的小括號中的程式,稱為「切入點(Pointcut)」,即是去指定哪個方法要被切面所切。

舉例來說,假設我們想要測量的是「HpPrinter 中的所有方法的時間」,那麼 HpPrinter 中的所有方法,就是切入點(Pointcut)。

所以在下面這一段程式中,在 @Before 後面的小括號中的程式,即是去指定「切入點(Pointcut)」,表示我們想要使用這個 MyAspect 的切面,去切哪些方法。

步驟二:查看前面的註解是什麼 #

確認好了切入點之後,接著就是查看前面所加上的 AOP 註解是什麼。

像是在這個例子中,我們所加上的就是 @Before 註解,而 @Before 的用途,就是表示要在切入點 「執行前」,去執行 @Before 下面的方法(也就是 before() 方法)。

所以簡單來說,前面的這個 @Before 註解,他指定的就是 「時機點」,而 @Before 所對應的時機點,就是在切入點的方法「執行前」執行。

因此在步驟二這裡,就是去確認「切面方法執行的時機點」。

補充:除了 @Before 之外,AOP 也有提供其他不同時機點的註解(像是 @After@Around)給我們使用 ,本篇文章後面也會介紹這兩個註解給大家。

步驟三:要執行的切面方法 #

當我們確認好「切入點」和「時機點」之後,最後就可以在下面的 before() 方法中,去實作切面的程式了。

像是在這一段程式中,我們就只有在 before() 方法裡面寫上一行程式,去輸出「I’m before」的資訊到 console 上。

小結:綜合上述的三個步驟 #

所以綜合上述的三個步驟,我們就可以去解讀這一段 AOP 的程式的含義是什麼了!

  • 步驟一:指定了「切入點」為「HpPrinter 中的所有方法」
  • 步驟二:在切入點的方法「執行之前」
  • 步驟三:執行下面的 before() 方法

因此最後的結果,就會長的像是下圖這樣:

在 Spring Boot 中練習 @Aspect 和 @Before #

看完了上述對 @Aspect@Before 的介紹之後,我們也可以實際到 Spring Boot 程式中,來練習這些程式,實際的去感受一下 Spring AOP 的「切面」到底是如何運作的。

所以首先我們先把之前所實作的 HpPrinter 給刪減一下,將 count 變數的相關程式刪掉,只留下輸出「HP 印表機: ……」的程式即可。

@Component
public class HpPrinter implements Printer {

    @Override
    public void print(String message) {
        System.out.println("HP 印表機: " + message);
    }
}

接著同樣是在 com.example.demo 這個 package 底下,我們去創建一個新的 MyAspect class 出來,並且在裡面添加下列的程式:

@Aspect
@Component
public class MyAspect {

    @Before("execution(* com.example.demo.HpPrinter.*(..))")
    public void before() {
        System.out.println("I'm before");
    }
}

接著只需要確保 MyController 中有注入 HpPrinter 進來,並且會去執行 print() 方法,這樣子就可以了。

實作完程式之後,接著就可以運行 Spring Boot 程式。

等到 Spring Boot 運行成功之後,大家可以打開瀏覽器訪問 http://localhost:8080/test,理論上要變的跟前面的文章一樣,在瀏覽器中出現一個「Hello World」的字串。

而這時候回到 IntelliJ 上的話,就可以看到在 console 中,多出現了兩行資訊,分別是「I’m before」以及「HP 印表機: Hello World」。

在 console 上之所以會出現這兩行資訊,就是因為在 Spring Boot 執行 HpPrinter 的 print() 方法之前,Spring AOP 就先去執行了 MyAspect 中的切面方法 before(),因此才會使得「I’m before」比「HP 印表機: Hello World」還要早輸出到 console 上。

因此透過這個例子,大家就可以體會到 Spring AOP 的強大功能了!有了 Spring AOP 之後,我們就可以在任意的時機點,去執行切面的程式,這樣子就可以把共同邏輯給拆分出來,統一的寫在切面中進行管理了。

所以透過 Spring AOP 的幫助,就可以達到程式的重複利用,並且也可以讓各個 class 更加專注在處理他自己的功能,再也不用去添加一堆不相關的程式了。

其他時機點的用法:@After、@Around #

在 Spring AOP 中,除了可以使用 @Before,去達到在方法「執行前」去執行切面之外,我們也是可以將 @Before 替換成 @After 或是 @Around,在不同的時機點去執行切面的。

在 Spring AOP 裡面,有三種時機點可以選擇:

  • @Before:在方法「執行前」執行切面
  • @After:在方法「執行後」執行切面
  • @Around:在方法「執行前」和「執行後」,執行切面

@After 的用法 #

@After 的寫法其實和 @Before 一模一樣,因此只要把 @Before 替換成 @After,這樣子就可以了。所以實際使用起來會是下面這個樣子:

@Aspect
@Component
public class MyAspect {

    @After("execution(* com.example.demo.HpPrinter.*(..))")
    public void after() {
        System.out.println("I'm after");
    }
}

@Around 的用法 #

@Around 因為寫起來比較複雜,因此此處僅提供程式給大家參考,如果大家有興趣,可以再上網搜尋 @Around 的相關介紹

@Aspect
@Component
public class MyAspect {

    @Around("execution(* com.example.demo.HpPrinter.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("I'm around before");

        // 執行切入點的方法,obj 為切入點方法執行的結果
        Object obj = pjp.proceed();

        System.out.println("I'm around after");
        return obj;
    }
}

補充一:切入點(Pointcut)如何撰寫? #

在前面的程式中,我們有在 @Before 後面的小括號中,加上一段看起來很長的程式,那一段程式就是在指定方法的切入點為何。

切入點的寫法是有一定的規則的,像是上面這段程式,就是表示切入點為「HpPrinter 的所有方法」。

不過由於切入點的語法寫起來還滿複雜的,並且使用頻率也不是很高,因此建議大家有個印象就好,真的需要實作時再去查詢就可以了。以下提供幾種常見的寫法邏輯給大家:

補充二:Spring AOP 的發展 #

了解了 Spring AOP 的用法之後,最後也跟大家補充一下 Spring AOP 的相關發展。

Spring AOP 以前最常被用在以下三個地方:

  • 權限驗證
  • 統一的 Exception 處理
  • Log 記錄

但是由於 Spring Boot 發展逐漸成熟,因此上述這些功能,都已經被封裝成更好用的工具讓我們使用了,所以大家目前其實已經比較少直接使用 @Aspect 去創建一個切面出來了。

不過,雖然 Spring AOP 已經漸漸淡出大家的日常使用,但是他作為 Spring 框架中的重要特性之一,還是會常常圍繞在我們周邊的,只是我們可能感覺不太到而已。

因此上述所介紹的 Spring AOP 的相關用法,大家就有個印象就可以了,重點是要把 AOP 的切面概念搞懂,至於 @Before@After@Around 的用法,只要有個印象就可以了。

總結 #

這篇文章我們先介紹了要如何透過 @Aspect@Before,在指定的時機點去執行切面方法,並且也簡單介紹了另外兩個時機點 @After@Around 的用法,最後我們也補充了切入點的撰寫方式,以及 Spring AOP 的相關發展。

那麼有關 Spring AOP 的介紹就到這邊結束了,Spring AOP 作為 Spring 框架中的重要特性之一,即使我們在日常的開發中,已經很少直接使用到 AOP 的功能,但是 AOP 的切面的概念,仍舊在許多功能中被廣泛應用,因此了解一下切面的概念還是很不錯的!

那麼從下一篇文章開始,我們就會進入到下一個部分:Spring MVC,介紹要如何在 Spring Boot 中和「前端」進行溝通,那我們就下一篇文章見啦!

補充:本文是擷取自我開設的線上課程 「Java 工程師必備!Spring Boot 零基礎入門」 的內容,如果你想了解更多的 Spring Boot 的用法,歡迎參考課程簡介 (輸入折扣碼「HH202504KU」即可享 85 折優惠)。