知識庫 / Spring / Spring Boot RSS 訂閱

AspectJ 包內所有方法的切入點

Spring Boot
HongKong
10
10:54 AM · Dec 06 ,2025

1. 概述

AspectJ 是一種強大的工具,用於處理 Java 應用程序中的橫切關注點,例如日誌記錄、安全性和事務管理。一個常見的使用場景是在特定包中的所有方法上應用一個方面。 在本教程中,我們將學習如何創建一個 AspectJ 斷言點(pointcut),該斷言點與特定包中的所有方法匹配,並提供分步代碼示例。

要了解更多關於 AspectJ 的信息,請查看我們的全面 AspectJ 教程。

2. Maven 依賴

運行 AspectJ 程序時,類路徑應包含類和方面,以及 AspectJ 運行時庫:aspectjrt

<dependency>
    <groupId>org.aspectj</groupId> 
    <artifactId>aspectjrt</artifactId>
    <version>1.9.22.1</version>
</dependency>

除了 AspectJ 運行時依賴,我們還需要包含 aspectjweaver 庫,以便在加載時將建議(advice)引入 Java 類:

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId> 
    <version>1.9.22.1</version>
</dependency>

3. 什麼是切點?

在 AspectJ 中,切點 (pointcut) 是一種核心概念,用於定義切片在代碼中的應用位置。切片用於管理橫切關注點,例如日誌記錄、安全性和事務管理。 切點指定了程序執行中特定的點,稱為連接點 (join point),這些連接點是切片建議 (或操作) 運行的地方。 這些連接點可以使用不同的表達式進行識別,包括方法簽名、類名或特定包。

3.1. 與切入點相關的關鍵概念

一個切入點是在程序執行過程中一個特定的時刻,可以在其中應用方面。這包括方法調用、執行、對象實例化和字段訪問。建議是指方面在切入點執行的操作。它可以在切入點之前(<em @Before</em>>)、之後(<em @After</em>>)或周圍(<em @Around</em>>)執行。切入點表達式是一個聲明,用於定義應匹配的切入點。該表達式遵循特定的語法,使其能夠針對方法執行、字段訪問以及其他內容。

3.2. Pointcut 語法

一個 Pointcut 表達式通常包含兩個關鍵組成部分:連接點的類型和簽名模式。連接點的類型定義了事件,包括方法調用、方法執行或構造執行。簽名模式則通過類、包、參數或返回值等標準來識別特定的方法或字段。

4. Pointcut 表達式

為了創建一個匹配特定包中所有方法的 Pointcut 表達式,可以使用以下表達式:

execution(* com.baeldung.aspectj..*(..))

以下是對該表達式的詳細分解:

  • 執行: 切面設計器指定我們正在針對方法執行。
  • *: 通配符,表示任何返回類型。
  • com.baeldung.aspectj..: 匹配 com.baeldung.aspectj 包及其任何子包中的任何類。
  • (..): 匹配任何方法參數。

4.1. 包內所有方法的日誌記錄方面

讓我們創建一個示例方面,用於記錄 com.baeldung.aspectj 包中所有方法的執行:

@Before("execution(* com.baeldung.aspectj..*(..))")
public void pointcutInsideAspectjPackage(JoinPoint joinPoint) {
    String methodName = joinPoint.getSignature().getName();
    String className = joinPoint.getTarget().getClass().getSimpleName();
    System.out.println(
        "Executing method inside aspectj package: " + className + "." + methodName
    );
}

切入點表達式在 @Before 目標中,覆蓋了 com.baeldung.aspectj 包及其所有子包中的所有方法。

讓我們在服務包中創建 UserService

@Service
public class UserService {
    public void createUser(String name, int age) {
        System.out.println("Request to create user: " + name + " | age: " + age);
    }

    public void deleteUser(String name) {
        System.out.println("Request to delete user: " + name);
    }
}

UserService 方法執行時,切面 pointcutInsideAspectjPackage() 將會記錄這些方法。現在,讓我們測試我們的代碼:

@Test
void testUserService() {
    userService.createUser("create new user john", 21);
    userService.deleteUser("john");
}

該方面 pointcutInsideAspectjPackage() 應在 createdUser()deleteUser()UserService 類中執行之前調用:

Executing method inside aspectj package: UserService.createUser
Request to create user: create new user john | age: 21
Executing method inside aspectj package: UserService.deleteUser
Request to delete user: john

接下來,讓我們在名為“倉庫”的包中創建一個名為 UserRepository 的類:

@Repository
public class UserRepository {
    public void createUser(String name, int age) {
        System.out.println("User: " + name + ", age:" + age + " is created.");
    }

    public void deleteUser(String name) {
        System.out.println("User: " + name + " is deleted.");
    }
}

UserRepository 類中的方法執行時,切面 pointcutInsideAspectjPackage() 會記錄這些方法。現在,讓我們測試我們的代碼:

@Test
void testUserRepository() {
    userRepository.createUser("john", 21);
    userRepository.deleteUser("john");
}

該方面 pointcutInsideAspectjPackage() 應在執行 createdUser()deleteUser() 方法以及 UserService 類中的代碼時立即調用:

Executing method inside aspectj package: UserRepository.createUser
User: john, age:21 is created
Executing method inside aspectj package: UserRepository.deleteUser
User: john is deleted.

4.2. 子包中所有方法的日誌方面

讓我們創建一個示例方面,用於記錄 com.baeldung.aspectj.service 包中所有方法的執行:

@Before("execution(* com.baeldung.aspectj.service..*(..))")
public void pointcutInsideServicePackage(JoinPoint joinPoint) {
    String methodName = joinPoint.getSignature().getName();
    String className = joinPoint.getTarget().getClass().getSimpleName();
    System.out.println(
        "Executing method inside service package: " + className + "." + methodName
    );
}

以下是 pointcut 表達式,位於 @Before 註解內的 (execution(* com.baeldung.aspectj.service..*(..))),匹配了 com.baeldung.aspectj.service 包中的所有方法。

接下來,讓我們創建一個名為 MessageService 的類,位於服務包中,以提供額外的測試用例:

@Service
public class MessageService {
    public void sendMessage(String message) {
        System.out.println("sending message: " + message);
    }

    public void receiveMessage(String message) {
        System.out.println("receiving message: " + message);
    }
}

當任何 MessageService 方法被執行時,切面 pointcutInsideServicePackage() 會記錄該方法。現在,讓我們測試我們的代碼:

@Test
void testMessageService() {
    messageService.sendMessage("send message from user john");
    messageService.receiveMessage("receive message from user john");
}

以下是翻譯後的內容:

以下方面 pointcutInsideAspectjPackage()pointcutInsideServicePackage() 應在調用 sendMessage()receiveMessage() 方法之前執行,這些方法位於 MessageService 類中:

Executing method inside aspectj package: MessageService.sendMessage
Executing method inside service package: MessageService.sendMessage 
sending message: send message from user john
Executing method inside aspectj package: MessageService.receiveMessage
Executing method inside service package: MessageService.receiveMessage
receiving message: receive message from user john

4.3. 通過排除特定包來實施日誌記錄方面

讓我們創建一個示例方面,該方面將排除對名為 com.baeldung.aspectj.service 的特定包的執行:

@Before("execution(* com.baeldung.aspectj..*(..)) && !execution(* com.baeldung.aspectj.repository..*(..))")
public void pointcutWithoutSubPackageRepository(JoinPoint joinPoint) {
    String methodName = joinPoint.getSignature().getName();
    String className = joinPoint.getTarget().getClass().getSimpleName();
    System.out.println(
        "Executing method without sub-package repository: " + className + "." + methodName
    );
}

以下是 pointcut 表達式,位於 @Before 註解內的 (execution(* com.baeldung.aspectj..*(..)) && !execution(* com.baeldung.aspectj.repository..*(..))),匹配了 com.baeldung.aspectj 包及其子包中的所有方法,並排除子包 repository。

現在,讓我們重新運行之前的單元測試。名為 pointcutWithoutSubPackageRepository 的切面應在所有 aspectj 包中的方法之前被調用,在此情況下,排除 repository 子包:

Executing method inside aspectj package: UserService.createUser
Executing method inside service package: UserService.createUser
Executing method without sub-package repository: UserService.createUser
Request to create user: create new user john | age: 21
Executing method inside aspectj package: UserService.deleteUser
Executing method inside service package: UserService.deleteUser
Executing method without sub-package repository: UserService.deleteUser
Request to delete user: john
Executing method inside aspectj package: UserRepository.createUser
User: john, age:21 is created.
Executing method inside aspectj package: UserRepository.deleteUser
User: john is deleted.
Executing method inside aspectj package: MessageService.sendMessage
Executing method inside service package: MessageService.sendMessage
Executing method without sub-package repository: MessageService.sendMessage
sending message: send message from user john
Executing method inside aspectj package: MessageService.receiveMessage
Executing method inside service package: MessageService.receiveMessage
Executing method without sub-package repository: MessageService.receiveMessage
receiving message: receive message from user john

5. 結論

在本教程中,我們瞭解到 AspectJ 中的 pointcut 是一種強大的工具,用於精確指定 aspect 建議應應用於哪些位置(例如,方法、類或字段)。

使用 AspectJ,創建 pointcut 以針對主包或特定包中的所有方法非常簡單。如果需要,還可以排除某些包。

這種方法對於在多個類和方法中應用相同的邏輯(例如,日誌記錄或安全檢查)而無需複製代碼非常有用。通過為所需的包定義 pointcut,您可以保持代碼整潔且易於維護。

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

發佈 評論

Some HTML is okay.