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 john4.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 john5. 結論
在本教程中,我們瞭解到 AspectJ 中的 pointcut 是一種強大的工具,用於精確指定 aspect 建議應應用於哪些位置(例如,方法、類或字段)。
使用 AspectJ,創建 pointcut 以針對主包或特定包中的所有方法非常簡單。如果需要,還可以排除某些包。
這種方法對於在多個類和方法中應用相同的邏輯(例如,日誌記錄或安全檢查)而無需複製代碼非常有用。通過為所需的包定義 pointcut,您可以保持代碼整潔且易於維護。