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>

此外还需引入aspectjweaver库,用于在类加载时织入通知:

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

3. 什么是切点?

切点(Pointcut)是AspectJ的核心概念,用于定义切面在代码中的应用位置。切面管理日志、安全等横切关注点。切点指定程序执行中的连接点(Join Point),在这些点执行切面的通知(Advice)。 连接点可通过方法签名、类名或包名等表达式识别。

3.1. 切点相关核心概念

  • 连接点:程序执行中可应用切面的特定时刻,包括方法调用/执行、对象实例化、字段访问等
  • 通知:切面在连接点执行的动作,分为:
    • @Before:前置通知
    • @After:后置通知
    • @Around:环绕通知
  • 切点表达式:声明匹配哪些连接点的表达式,遵循特定语法,可定位方法执行、字段访问等

3.2. 切点语法

切点表达式通常包含两个关键组件:

  1. 连接点类型:定义事件类型(方法调用/执行、构造器执行等)
  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包及其子包的所有方法。

在service包创建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");
}

输出结果:

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

在repository包创建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.");
    }
}

执行测试:

@Test
void testUserRepository() {
    userRepository.createUser("john", 21);
    userRepository.deleteUser("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.

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包的所有方法。

在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);
    }
}

执行测试:

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

输出结果(两个切面均生效):

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.repository包:

@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
    );
}

表达式execution(* com.baeldung.aspectj..*(..)) && !execution(* com.baeldung.aspectj.repository..*(..))匹配com.baeldung.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

✅ 注意:UserRepository的方法未被pointcutWithoutSubPackageRepository切面拦截

5. 总结

本教程学习了AspectJ切点的核心概念:

  • 切点:精确定位切面通知应用位置(方法/类/字段)的强大工具
  • 包级切点:使用AspectJ为整个包或特定包创建切点非常简单
  • 排除机制:可通过逻辑运算符排除特定包

这种方法特别适合在多个类和方法间统一应用日志、安全检查等逻辑,避免代码重复。通过为目标包定义切点,能保持代码整洁且易于维护。


原始标题:AspectJ Pointcut for All Methods Inside Package | Baeldung