包內所有方法的 AspectJ 切入點
1. 概述
AspectJ 是一個強大的工具,用於處理 Java 應用程式中的橫切問題,例如日誌記錄、安全性和事務管理。一個常見的用例是將一個方面應用於特定包中的所有方法。在本教程中,我們將學習如何在 AspectJ 中建立一個匹配包中所有方法的切入點,並提供逐步程式碼範例。
要了解有關 AspectJ 的更多信息,請查看我們全面的 AspectJ 教程。
2.Maven依賴
執行 AspectJ 程式時,類別路徑應包含類別和方面,以及 AspectJ 運行時庫aspectjrt :
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.22.1</version>
</dependency>
除了 AspectJ 執行時期依賴項之外,我們還需要包含[aspectjweaver](https://mvnrepository.com/artifact/org.aspectj/aspectjweaver/1.9.22.1)庫,以便在載入時向 Java 類別引入建議:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.22.1</version>
</dependency>
3. 什麼是切入點?
AspectJ 中的切入點是定義切面應在程式碼中應用的核心概念。方面管理橫切關注點,例如日誌記錄、安全性或事務管理。切入點指定程式執行中應執行方面建議(或操作)的特定點(稱為連接點)。這些連接點可以使用不同的表達式來標識,包括方法簽名、類別名稱或特定的套件。
3.1.與切入點相關的關鍵概念
連接點是程式執行中可以應用方面的特定時刻。這包括方法呼叫、執行、物件實例化和欄位存取。建議是方面在連接點採取的操作。它可以發生在連接點之前 ( @Before )、之後 ( @After ) 或周圍 ( @Around )。切入點表達式是定義應符合哪些連接點的宣告。此表達式遵循特定的語法,使其能夠定位方法執行、欄位存取等。
3.2.切入點文法
切入點表達式通常有兩個關鍵組成部分:連接點的類型和簽名模式。連接點的型別定義事件,包括方法呼叫、方法執行或建構函式執行。簽名模式使用類別、套件、參數或傳回類型標準來識別特定的方法或欄位。
4. 切入點表達式
要建立符合特定套件中所有方法的切入點,我們可以使用以下表達式:
execution(* com.baeldung.aspectj..*(..))
下面是這個表達式的細分:
-
execution:切入點指示符,指定我們的目標是方法執行。 - *:表示任何傳回類型的通配符。
-
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");
}
應在執行UserService類別中的createdUser()和deleteUser()之前呼叫切面pointcutInsideAspectjPackage() :
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");
}
應在執行UserService類別中的createdUser()和deleteUser()方法之前呼叫切面pointcutInsideAspectjPackage() :
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
);
}
@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()應該在呼叫MessageService類別中的方法sendMessage()和receiveMessage()之前呼叫:
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
);
}
@Before註解中的切入點表達式(execution(* com.baeldung.aspectj..*(..)) && !execution(* com.baeldung.aspectj.repository..*(..)))符合其中的所有方法com.baeldung.aspectj套件及其子包,不包括子包存儲庫。
現在,讓我們重新運行先前的單元測試。名為pointcutWithoutSubPackageRepository()切面應在切面j包中的所有方法之前調用,同時在這種情況下排除存儲庫子包:
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 中的切入點是一個強大的工具,用於準確指定應應用方面建議的位置(例如方法、類別或欄位)。
使用 AspectJ 建立切入點來定位主套件或特定套件中的所有方法非常簡單。如果需要,我們也可以排除某些套件。
此方法對於在多個類別和方法之間應用相同的邏輯(例如日誌記錄或安全檢查)非常有用,而無需重複程式碼。透過為所需的套件定義切入點,我們可以讓您的程式碼保持乾淨且易於維護。
程式碼範例可在 GitHub 上取得。