Spring环绕通知(Around Advice)
外观
Spring环绕通知(Around Advice)[编辑 | 编辑源代码]
环绕通知是Spring AOP中最强大的通知类型,它允许开发者**完全控制目标方法的执行过程**。与其他通知不同,环绕通知可以决定是否执行目标方法、修改参数值或返回值,甚至捕获和处理异常。
核心特性[编辑 | 编辑源代码]
- 完全控制流程:通过
ProceedingJoinPoint
决定是否调用目标方法 - 参数与返回值处理:可修改方法参数和返回值
- 异常处理:能捕获并转换异常
- 性能监控:常用于记录方法执行时间
语法结构[编辑 | 编辑源代码]
基本语法示例:
@Around("execution(* com.example.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// 前置逻辑
Object result = joinPoint.proceed(); // 调用目标方法
// 后置逻辑
return result;
}
关键组件解析[编辑 | 编辑源代码]
组件 | 说明 |
---|---|
@Around |
声明环绕通知的注解 |
ProceedingJoinPoint |
提供访问目标方法信息的接口 |
proceed() |
执行目标方法的核心方法 |
完整示例[编辑 | 编辑源代码]
以下是一个包含性能监控和异常处理的完整案例:
@Aspect
@Component
public class MonitoringAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object monitorMethod(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
try {
// 可在此修改参数
Object[] args = pjp.getArgs();
if (args.length > 0 && args[0] instanceof String) {
args[0] = ((String) args[0]).trim();
}
Object result = pjp.proceed(args);
// 可在此修改返回值
if (result instanceof List) {
((List<?>) result).removeIf(Objects::isNull);
}
return result;
} catch (Exception e) {
// 异常转换示例
throw new ServiceException("操作失败: " + e.getMessage(), e);
} finally {
long duration = System.currentTimeMillis() - start;
System.out.printf("方法 %s 执行耗时: %dms%n",
pjp.getSignature().getName(), duration);
}
}
}
执行流程[编辑 | 编辑源代码]
实际应用场景[编辑 | 编辑源代码]
1. 事务管理[编辑 | 编辑源代码]
通过环绕通知实现声明式事务:
@Around("@annotation(transactional)")
public Object manageTransaction(ProceedingJoinPoint pjp, Transactional transactional) throws Throwable {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
Object result = pjp.proceed();
transactionManager.commit(status);
return result;
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
2. 缓存控制[编辑 | 编辑源代码]
实现缓存-穿透保护模式:
@Around("@annotation(cacheable)")
public Object checkCache(ProceedingJoinPoint pjp, Cacheable cacheable) throws Throwable {
String key = generateCacheKey(pjp);
Object cached = cache.get(key);
if (cached != null) return cached;
Object result = pjp.proceed();
if (result != null) {
cache.put(key, result, cacheable.expire());
}
return result;
}
高级技巧[编辑 | 编辑源代码]
嵌套调用处理[编辑 | 编辑源代码]
当多个环绕通知作用于同一方法时,执行顺序由aspect的@Order
注解决定:
@Aspect
@Order(1)
public class LoggingAspect {
@Around("execution(* com..*(..))")
public Object log(ProceedingJoinPoint pjp) throws Throwable {
// 日志逻辑
return pjp.proceed();
}
}
@Aspect
@Order(2)
public class SecurityAspect {
@Around("execution(* com..*(..))")
public Object checkPermission(ProceedingJoinPoint pjp) throws Throwable {
// 权限检查
return pjp.proceed();
}
}
性能优化建议[编辑 | 编辑源代码]
- 避免在环绕通知中执行耗时操作
- 对高频调用方法考虑使用
@Around
的if()
条件 - 使用
joinPoint.getArgs()
的缓存结果
常见问题[编辑 | 编辑源代码]
页面模块:Message box/ambox.css没有内容。
环绕通知中必须调用`proceed()`,否则目标方法不会执行 |
问题 | 解决方案 |
---|---|
忘记调用proceed() | 使用IDE模板确保基本结构正确 |
修改不可变参数 | 对String等不可变对象需要特殊处理 |
异常处理覆盖 | 确保不会意外吞没关键异常 |
数学表达[编辑 | 编辑源代码]
环绕通知的执行时间公式: 其中:
- = 前置处理时间
- = 目标方法执行时间
- = 后置处理时间