Spring AOP 全面学习:切面编程的 6 种通知类型
Spring AOP 完整学习项目,覆盖 @Before/@After/@AfterReturning/@AfterThrowing/@Around 通知和自定义注解切面。
Spring AOP 全面学习:6 种通知类型与自调用陷阱
通知类型一览
| 通知 | 注解 | 触发时机 | 典型用途 |
|---|---|---|---|
| 前置通知 | @Before | 方法执行前 | 参数校验、权限检查 |
| 后置通知 | @After | 方法执行后(无论成功或异常) | 资源清理 |
| 返回通知 | @AfterReturning | 方法成功返回后 | 成功日志、数据脱敏 |
| 异常通知 | @AfterThrowing | 方法抛出异常后 | 异常告警、事务回滚记录 |
| 环绕通知 | @Around | 方法执行前后,完全控制 | 性能监控、缓存、重试 |
| 引入通知 | @DeclareParents | 类加载时 | 动态引入新接口 |
切入点表达式
@Pointcut("execution(public * com.example.service.*.*(..))")
private void serviceLayer() {}
| 表达式 | 匹配 | 说明 |
|---|---|---|
execution(* com.example.service.*.*(..)) | service 包下所有类的所有方法 | 最常用 |
execution(* com.example..*.*(..)) | com.example 包及其子包 | 递归匹配 |
within(com.example.service.*) | service 包下的所有类 | 类级别匹配 |
@annotation(com.example.annotation.Monitor) | 标注了 @Monitor 的方法 | 注解驱动 |
bean(userService) | 名为 userService 的 Bean | 按名称匹配 |
组合使用
@Around("execution(* com.example.service.*.*(..)) && @annotation(monitor)")
public Object monitor(ProceedingJoinPoint pjp, Monitor monitor) throws Throwable {
// 只拦截 service 包下标注了 @Monitor 的方法
}
自调用陷阱(最容易被忽略的坑)
@Service
public class UserService {
@Transactional
public void createUser(User user) {
userRepository.save(user);
}
public void batchCreate(List<User> users) {
for (User user : users) {
this.createUser(user); // @Transactional 不生效!
}
}
}
AOP 的底层原理是代理。Spring 创建了 UserService 的代理对象,但 this 指向的是原始对象,不是代理对象。所以通过 this.createUser() 调用时不会触发 AOP 增强。
解决方案
方案一:注入自身代理(推荐)
@Service
public class UserService {
@Autowired
private UserService self; // 注入的是代理对象
public void batchCreate(List<User> users) {
for (User user : users) {
self.createUser(user); // 通过代理调用,AOP 生效
}
}
}
方案二:AopContext.currentProxy()
@Service
public class UserService {
public void batchCreate(List<User> users) {
UserService proxy = (UserService) AopContext.currentProxy();
for (User user : users) {
proxy.createUser(user);
}
}
}
需要配置 @EnableAspectJAutoProxy(exposeProxy = true)。
方案三:提取到独立的 Service
@Service
public class UserService {
@Autowired
private UserCreationService creationService; // 另一个 Bean
public void batchCreate(List<User> users) {
for (User user : users) {
creationService.createUser(user); // 跨 Bean 调用,AOP 生效
}
}
}
实际应用
在我的项目中,AOP 的主要落地场景:
- 接口耗时监控
@Around("@annotation(org.springframework.web.bind.annotation.GetMapping)")
public Object monitorApi(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
try {
return pjp.proceed();
} finally {
long elapsed = System.currentTimeMillis() - start;
if (elapsed > 1000) {
log.warn("慢接口: {} 耗时 {}ms", pjp.getSignature(), elapsed);
}
}
}
- @Cacheable 注解缓存:Spring 自带的缓存注解基于 AOP
- 自定义 @AuditLog 操作日志:记录增删改操作的用户和参数
代理机制
Spring AOP 使用两种代理:
- JDK 动态代理:目标类实现了接口时使用。代理接口方法。
- CGLIB 代理:目标类没有接口时使用。生成子类代理。
注意:CGLIB 代理无法代理 final 方法和 private 方法。Spring Boot 2.0+ 默认使用 CGLIB。