知識庫 / Spring RSS 訂閱

自定義 Spring AOP 註解實施指南

Spring
HongKong
10
02:36 PM · Dec 06 ,2025

1. 簡介

本文將使用 Spring 中的 AOP 支持,實現自定義 AOP 註解。

首先,我們將對 AOP 進行高層次概述,解釋其是什麼以及它的優勢。隨後,我們將逐步實現我們的註解,隨着步驟的推進,逐步深入理解 AOP 概念。

最終,您將對 AOP 有更深入的理解,並具備在未來創建自定義 Spring 註解的能力。

2. 什麼是 AOP 註解?

簡而言之,AOP 代表面向方面編程。本質上,它是一種在不修改現有代碼的情況下添加行為的方式

對於 AOP 的詳細介紹,請參考關於 AOP 切面和建議的文章。本文假設您已經具備一定的基礎知識。

在本篇文章中,我們將實現的是基於註解的 AOP。如果您已經熟悉 Spring 中的 @Transactional 註解,那麼您可能已經對此有所瞭解。

@Transactional
public void orderGoods(Order order) {
   // A series of database calls to be performed in a transaction
}

關鍵在於非侵入性。通過使用標註元數據,我們的核心業務邏輯不會被交易代碼污染。這使得我們更容易理解、重構和隔離測試。

有時,開發 Spring 應用的人可能會將這視為“Spring 魔法”,而沒有深入思考其工作原理。實際上,發生的事情並不複雜。但是,一旦我們完成了本文中的步驟,我們就能創建自己的自定義註解,以便理解和利用 AOP。

3. Maven 依賴

首先,我們添加我們的 Maven 依賴

對於這個例子,我們將使用 Spring Boot,其約定優於配置的方法讓我們能夠儘快上手:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.2.RELEASE</version>
</parent>

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

請注意,我們包含了AOP啓動器,它引入了我們實現方面所需的庫。

4. 創建我們的自定義標註

我們將要創建的標註將用於記錄方法執行所需的時間。 讓我們創建我們的標註:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {

}

雖然這是一個相對簡單的實現,但值得注意的是這兩個元標註的用途。

<a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/annotation/Target.html">@Target</a> 標註告訴我們標註將適用的位置。在這裏,我們使用了 ElementType.Method,這意味着它僅適用於方法。如果我們嘗試在其他地方使用該標註,則代碼將無法編譯。這種行為是合理的,因為我們的標註將用於記錄方法的執行時間。

<a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/annotation/Retention.html">@Retention</a> 僅聲明標註在運行時是否可用。默認情況下它不可見,因此 Spring AOP 將無法看到該標註。這就是為什麼它被重新配置的原因。

5. 創建我們的方面

現在我們已經有了標註,接下來我們將創建我們的方面。這只是一個模塊,它將封裝我們的橫切關注點,在本例中是方法執行時間日誌記錄。它本質上是一個類,該類已使用 @Aspect 標註。

@Aspect
@Component
public class ExampleAspect {

}

我們還包含了@Component註解,因為我們的類也需要成為 Spring Bean 才能被檢測到。本質上,這是我們將要實現我們自定義註解注入的邏輯所在。

6. 創建我們的 Pointcut 和 Advice

現在,讓我們創建我們的 pointcut 和 advice。這將是一個帶有註釋的方法,該方法位於我們的方面中。

@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
    return joinPoint.proceed();
}

技術上講,這尚未改變任何東西的行為,但仍有大量內容需要分析。

首先,我們已使用 @Around 對方法進行了註釋。這是一種建議,而環繞建議意味着我們在方法執行前後添加了額外的代碼。還有其他類型的建議,例如 before after,但它們將超出本文的範圍。

接下來,我們的 @Around 註解具有一個切點參數。我們的切點只是説,“應用此建議的任何已使用 @LogExecutionTime 註解的方法。” 還有其他類型的切點,但它們也將超出範圍。

方法 logExecutionTime() 本身就是我們的建議。它有一個參數,ProceedingJoinPoint。 在我們的例子中,這將是一個正在執行且已使用 @LogExecutionTime 註解的方法。

最後,當我們的註釋方法被調用時,會首先調用我們的建議。然後,建議決定下一步該怎麼做。 在我們的例子中,我們的建議只是調用 proceed(),即調用原始註釋的方法。

7. 記錄執行時間

現在我們已經搭建了基本框架,只需要在建議中添加一些額外的邏輯,即可記錄執行時間,同時調用原始方法。 讓我們為建議添加這種行為:

@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
    long start = System.currentTimeMillis();

    Object proceed = joinPoint.proceed();

    long executionTime = System.currentTimeMillis() - start;

    System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");
    return proceed;
}

再次強調,這裏並沒有進行任何特別複雜的操作。我們只是記錄了當前時間,執行了方法,然後將耗時打印到控制枱。我們還記錄了方法的簽名,這對於使用 joinpoint 實例至關重要。我們還可以訪問其他信息,例如方法參數。

現在,讓我們嘗試使用 @LogExecutionTime 註解一個方法,然後執行它來查看結果。請注意,這必須是一個 Spring Bean 才能正確工作:

@LogExecutionTime
public void serve() throws InterruptedException {
    Thread.sleep(2000);
}

執行完成後,我們應該在控制枱中看到以下內容:

void org.baeldung.Service.serve() executed in 2030ms

8. 結論在本文中,我們利用 Spring Boot AOP 創建了自定義註解,並將其應用於 Spring Bean,從而在運行時向這些 Bean 注入額外的行為。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.