Spring AOP环绕通知最适合统计方法耗时,核心是用ProceedingJoinPoint在proceed()前后调用System.nanoTime()计时,确保异常时仍能统计,并推荐结合阈值告警与异步日志避免性能影响。

Spring AOP 的环绕通知(@Around)是最灵活的通知类型,它能控制目标方法是否执行、何时执行、如何执行,也天然适合做方法耗时统计——只需在调用前记录开始时间,调用后计算差值即可。
核心思路:用 ProceedingJoinPoint 拦截并包裹方法执行
环绕通知的参数必须是 ProceedingJoinPoint(不是普通 JoinPoint),它提供 proceed() 方法来真正触发原方法。耗时统计就发生在 proceed() 前后:
- 调用
System.nanoTime()或System.currentTimeMillis()记录起始时间(推荐nanoTime,精度更高) - 执行
joinPoint.proceed(),这会运行原始方法逻辑(可能抛异常) - 再取一次时间,相减得出耗时(单位纳秒或毫秒)
- 把耗时、方法签名、参数等信息打印或写入日志/监控系统
基础实现示例
以下是一个统计 Controller 层方法耗时的简单环绕通知:
@Aspect
@Component
public class TimingAspect {
<pre class="brush:php;toolbar:false;">private static final Logger log = LoggerFactory.getLogger(TimingAspect.class);
@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping) || " +
"@annotation(org.springframework.web.bind.annotation.GetMapping) || " +
"@annotation(org.springframework.web.bind.annotation.PostMapping)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.nanoTime();
Object result;
try {
result = joinPoint.proceed(); // 执行目标方法
} catch (Throwable e) {
throw e; // 不要吞异常
}
long end = System.nanoTime();
long durationMs = TimeUnit.NANOSECONDS.toMillis(end - start);
String signature = joinPoint.getSignature().toShortString();
log.info("Method '{}' executed in {} ms", signature, durationMs);
return result;
}}
增强实用性:支持条件开关与分级告警
生产环境通常不希望所有方法都打日志,可结合配置或注解动态控制:
立即学习“Java免费学习笔记(深入)”;
- 定义自定义注解如
@LogTiming(thresholdMs = 100),只对标注了该注解且耗时超阈值的方法记录 - 用
@Value("${timing.enabled:true}")控制整个切面是否生效 - 对耗时 > 1s 的方法额外发告警(如写入 Sentry、推送企业微信)
- 避免打印敏感参数(如密码字段),可用
joinPoint.getArgs()过滤或脱敏
注意事项与常见坑
实际使用中容易忽略的关键点:
-
必须调用
proceed():否则目标方法不会执行,且不能写成joinPoint.proceed(); return null;—— 要返回proceed()的结果 -
异常处理要完整:如果目标方法抛异常,
proceed()会直接抛出,需在catch中捕获并重新抛出,否则耗时统计中断且业务异常丢失 -
不要在环绕通知里修改
args后直接调用proceed():它不会自动应用修改;如需改参,应调用proceed(Object[] args)显式传入 - 避免在耗时统计中做重操作:比如同步调用远程服务、写磁盘日志,否则会拖慢真实响应——建议异步记录或使用高性能日志框架(如 Logback 异步 Appender)


















