可用于Java包内所有方法的 AspectJ 切入点

在本教程中,我们了解到 AspectJ 中的切入点是一个强大的工具,可以精确指定方面建议的应用位置(例如方法、类或字段)。

使用 AspectJ 可以轻松创建切入点以定位主包或特定包中的所有方法。我们还可以根据需要排除某些包。

此方法对于在多个类和方法中应用相同的逻辑(例如日志记录或安全检查)非常有用,而无需重复代码。通过为所需的包定义切入点,我们可以让您的代码保持干净且易于维护。

AspectJ 是一款功能强大的工具,可用于处理 Java 应用程序中的日志记录、安全性和事务管理等横切关注点。一种常见用例是将一个方面应用于特定包中的所有方法。在本教程中,我们将学习如何在 AspectJ 中创建一个与包中的所有方法匹配的切入点,并提供分步代码示例。

要了解有关 AspectJ 的更多信息,请查看我们全面的 AspectJ 教程。

Maven 依赖项
运行 AspectJ 程序时,类路径应包含类和方面,以及 AspectJ 运行时库 aspectsjrt:

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

除了 AspectJ 运行时依赖项之外,我们还需要包含 aspectsjweaver 库,以便在加载时向 Java 类引入建议:

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

什么是切入点?
AspectJ 中的切入点是一个核心概念,它定义了方面应在代码中的何处应用。方面管理横切关注点,如日志记录、安全或事务管理。切入点指定程序执行过程中应运行方面建议(或操作)的特定点(称为连接点)。可以使用不同的表达式来识别这些连接点,包括方法签名、类名或特定包。

1. 与切入点相关的关键概念
连接点是程序执行中可以应用方面的具体时刻。这包括方法调用、执行、对象实例化和字段访问。建议是方面在连接点采取的操作。它可以发生在连接点之前 ( @Before )、之后 ( @After ) 或周围 ( @Around )。切入点表达式是一种定义应匹配哪些连接点的声明。此表达式遵循特定语法,使其能够定位方法执行、字段访问等。

3.2. 切入点语法
切入点表达式通常有两个关键组成部分:连接点类型和签名模式。连接点类型定义事件,包括方法调用、方法执行或构造函数执行。签名模式使用类、包、参数或返回类型标准来标识特定方法或字段。

4.切入点表达式
要创建与特定包中的所有方法匹配的切入点,我们可以使用以下表达式:

execution(* com.jdon.aspectj..*(..))

以下是此表达式的详细说明:

  • execution:切入点指示符,指定我们的目标是方法执行。
  • *:表示任意返回类型的通配符。
  • com.jdon.aspectj.. *:匹配com.jdon.aspectj包及其子包中的任何类。
    *:匹配任何方法参数。

4.1. 包中所有方法的日志记录方面
让我们创建一个示例方面,记录名为com.jdon.aspectj 的包中所有方法的执行情况:

@Before("execution(* com.jdon.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.jdon.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()应该在UserService类中的createdUser()和deleteUser()执行之前被调用:

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()应该在UserService类中的createdUser()和deleteUser()方法执行之前调用:

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.jdon.aspectj.service 的包中所有方法的执行情况:

@Before("execution(* com.jdon.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.jdon.aspectj.service..*(..)))与com.jdon.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()和acceptMessage()之前调用:

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.jdon.aspectj.service的特定包的执行:

@Before("execution(* com.jdon.aspectj..*(..)) && !execution(* com.jdon.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.jdon.aspectj..*(..)) && !execution(* com.jdon.aspectj.repository..*(..)))匹配com.jdon.aspectj包及其子包内的所有方法,但不包括子包存储库。

现在,让我们重新运行之前的单元测试。名为pointcutWithoutSubPackageRepository()的方面应该在 aspectsj 包中的所有方法之前调用,同时排除存储库子包:

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