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. 切点语法
切点表达式通常包含两个关键组件:
- 连接点类型:定义事件类型(方法调用/执行、构造器执行等)
- 签名模式:通过类、包、参数、返回类型等条件识别特定方法或字段
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为整个包或特定包创建切点非常简单
- 排除机制:可通过逻辑运算符排除特定包
这种方法特别适合在多个类和方法间统一应用日志、安全检查等逻辑,避免代码重复。通过为目标包定义切点,能保持代码整洁且易于维护。