在Spring AOP中,五种通知类型各有其独特的执行时机和适用场景。选择正确的通知类型,可以让你的代码更加清晰、高效。为了让你快速建立整体认知,下面这个表格清晰地对比了它们的核心特性。
| 通知类型 | 核心执行时机 | 能否阻止目标方法执行 | 典型应用场景 |
|---|---|---|---|
@Around(环绕通知) |
目标方法执行前后 | 可以 (不调用proceed()则方法不执行) |
性能监控、事务管理、缓存、限流 |
@Before(前置通知) |
目标方法执行前 | 不能 (除非抛出异常) | 权限校验、参数校验、日志记录 |
@AfterReturning(返回通知) |
目标方法成功返回后 | 不能 | 记录成功日志、对结果进行后处理(如脱敏) |
@AfterThrowing(异常通知) |
目标方法抛出异常后 | 不能 | 记录错误日志、告警、异常统计 |
@After(后置通知) |
目标方法执行后(无论成败) | 不能 | 资源清理、释放锁 |
💡 详解各类通知的应用场景
1. @Around:功能最强大的"总管"
这是最强大和灵活的通知,可以完全控制目标方法的执行。
- 典型场景 :性能监控、声明式事务管理、缓存、限流。
- 代码示例:计算方法执行耗时。
java
@Aspect
@Component
public class PerformanceAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object monitorPerformance(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
try {
// 执行目标方法
return pjp.proceed();
} finally {
long duration = System.currentTimeMillis() - start;
String methodName = pjp.getSignature().getName();
System.out.println("【性能监控】方法 " + methodName + " 执行耗时: " + duration + "ms");
}
}
}
2. @Before:尽职的"安全检查员"
在业务逻辑之前执行,适合做前置校验和记录。
- 典型场景 :权限验证、参数校验、记录方法开始日志。
- 代码示例:简单的参数校验。
less
@Aspect
@Component
public class ValidationAspect {
@Before("@annotation(org.springframework.web.bind.annotation.PostMapping)")
public void validateParams(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg == null) {
throw new IllegalArgumentException("请求参数不能为null");
}
}
}
}
3. @AfterReturning:乐观的"成功处理员"
只在方法成功返回后执行,可以获取到方法的返回值。
- 典型场景 :记录成功操作日志、对返回结果进行脱敏或格式化。
- 代码示例:对返回结果中的敏感信息进行脱敏。
less
@Aspect
@Component
public class DataMaskingAspect {
@AfterReturning(
pointcut = "execution(* com.example.service.UserService.getUserInfo(..))",
returning = "result"
)
public void maskSensitiveData(Object result) {
if (result instanceof UserDTO) {
UserDTO user = (UserDTO) result;
// 对手机号进行脱敏
String phone = user.getPhone();
user.setPhone(phone.replaceAll("(\d{3})\d{4}(\d{4})", "$1****$2"));
}
}
}
4. @AfterThrowing:专业的"故障排查员"
只在目标方法抛出异常时执行,用于处理错误情况。
- 典型场景 :记录详细的错误日志、发送告警通知、进行异常统计。
- 代码示例:异常日志记录与告警。
less
@Aspect
@Component
public class ExceptionLogAspect {
@AfterThrowing(
pointcut = "execution(* com.example..*.*(..))",
throwing = "ex"
)
public void logException(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().toShortString();
// 1. 记录详细的错误日志,方便排查
System.err.println("方法 [" + methodName + "] 执行异常: " + ex.getMessage());
// 2. (可选) 集成告警系统,发送邮件或短信
// alertService.sendAlert("系统异常告警", "异常方法: " + methodName, ex);
}
}
5. @After:可靠的"后勤保障员"
无论方法是成功返回还是抛出异常,都会执行,类似于 try-catch-finally中的 finally块。
- 典型场景 :释放资源(如文件流、数据库连接)、清理临时数据。
- 代码示例:资源清理。
less
@Aspect
@Component
public class ResourceCleanupAspect {
@After("execution(* com.example.service.FileService.process(..))")
public void cleanupResources(JoinPoint joinPoint) {
// 模拟释放资源,如关闭文件流、数据库连接等
System.out.println("正在释放方法执行过程中占用的资源...");
// resourceManager.cleanup(); // 实际的清理逻辑
}
}
🎯 选择策略与最佳实践
- 遵循"最小权限"原则 :如果
@Before就能满足需求(如参数校验),就不要用@Around。代码越简单,出错的概率越低。 @Around是万能药,但亦有代价:它最强大,但也最复杂,使用不当可能导致目标方法未执行或异常被吞掉。务必在必要时(需要控制方法执行或处理异常)才使用它。- 注意执行顺序 :当多个切面作用于同一方法时,可以使用
@Order注解控制顺序。通常,像权限校验这类切面应具有最高优先级(@Order值最小),最先执行。 - 警惕"自调用"问题 :同一个类内部的方法调用,不会触发AOP代理。例如,在
ServiceA的方法A中直接调用自己的方法B,方法B上的AOP通知是不会生效的。