
Spring AOP 的 @Around 通知若在 proceed() 后计算耗时,将无法捕获方法抛出异常时的执行时间;应改用 try-finally 结构确保计时逻辑始终执行。
spring aop 的 `@around` 通知若在 `proceed()` 后计算耗时,将无法捕获方法抛出异常时的执行时间;应改用 `try-finally` 结构确保计时逻辑始终执行。
在使用 Spring AOP 实现方法执行耗时监控时,一个常见误区是将耗时统计逻辑(如 endTime - startTime)写在 proceedingJoinPoint.proceed() 调用之后。这种写法看似合理,但存在严重缺陷:当目标方法抛出异常时,proceed() 会直接向上抛出异常,导致后续的耗时记录代码永远不会执行——这正是你遇到 Pointcut not working for method which throws Exception 的根本原因。
正确的做法是将耗时统计逻辑移至 finally 块中,确保无论方法正常返回还是抛出异常,计时逻辑都能被执行。以下是推荐的实现方式:
@Pointcut("execution(* com.customer.service.ExecuteCustomerService.initiateExecuteCustomer(..))")
private void logExecuteCustomer() {}
@Around("logExecuteCustomer()")
public Object TimerMonitoring(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
try {
return proceedingJoinPoint.proceed(); // 执行原方法,可能抛出 CustomerException 等异常
} finally {
// ✅ 无论成功或异常,此处必执行
long duration = System.currentTimeMillis() - startTime;
try {
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
String desc = signature.getDeclaringType().getSimpleName()
+ "." + signature.getName();
metricsService.metricsTimer(desc, duration);
} catch (Exception e) {
log.error("Failed to record metrics for {}", proceedingJoinPoint, e);
}
}
}
⚠️ 关键注意事项:
- 不要将 metricsService.metricsTimer(...) 放在 catch 或 return 之后——它必须位于 finally 中;
- proceed() 调用本身不捕获异常,而是透传原始异常,因此 @Around 方法签名需声明 throws Throwable;
- 若无需精确方法签名信息,可直接使用 proceedingJoinPoint.toString() 作为指标标识符,进一步简化逻辑并避免类型转换风险;
- 确保 metricsService 是 Spring 管理的 Bean(即被 @Component 或 @Service 注解),且已正确注入到切面类中,否则 NullPointerException 将掩盖真正的计时问题。
通过该结构,即使 initiateExecuteCustomer(...) 抛出 CustomerException,方法从开始到异常抛出的时间仍会被准确记录,真正实现“全路径”执行耗时监控。











