
当目标方法声明抛出受检异常(如 throws customerexception)时,若 @around 通知中未正确处理异常流程,可能导致监控逻辑在方法抛出异常时被跳过——根本原因在于 proceed() 后的统计代码未在异常路径下执行。
当目标方法声明抛出受检异常(如 throws customerexception)时,若 @around 通知中未正确处理异常流程,可能导致监控逻辑在方法抛出异常时被跳过——根本原因在于 proceed() 后的统计代码未在异常路径下执行。
在 Spring AOP 中,@Around 通知本质上是一个环绕增强,它完全控制目标方法的执行流程。常见误区是将耗时统计逻辑写在 proceed() 调用之后、且未包裹在 finally 块中,例如:
Object result = proceedingJoinPoint.proceed(); // ⚠️ 若此处抛出异常,后续计时逻辑将被跳过 long endTime = System.currentTimeMillis(); // ← 永远不会执行! metricsService.metricsTimer(...); return result;
这会导致:无论目标方法正常返回还是抛出异常(包括 CustomerException),只要 proceed() 抛出异常,endTime 计算和指标上报就会被中断,造成监控数据丢失——并非切点表达式失效,而是通知逻辑本身存在执行路径缺陷。
✅ 正确做法是将耗时统计与上报逻辑置于 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 {
// ✅ 保证始终执行:无论 proceed() 是否抛异常,此处都会运行
try {
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
String desc = signature.getDeclaringType().getSimpleName()
+ "." + signature.getName();
long duration = System.currentTimeMillis() - startTime;
metricsService.metricsTimer(desc, duration);
} catch (Exception e) {
log.error("Failed to record metrics for {}", proceedingJoinPoint, e);
}
}
}
? 进阶建议:
- 使用 proceedingJoinPoint.toString() 替代手动拼接方法签名,更简洁且避免空指针风险;
- 若仅需粗粒度监控,可省略 MethodSignature 解析,直接记录 joinPoint 字符串表示;
- 避免在 finally 中抛出新异常(会掩盖原始异常),因此内部 metricsService 调用务必用 try-catch 包裹;
- 确保 metricsService 自身线程安全且低延迟,防止监控逻辑拖慢主业务。
总结:切点表达式 execution(* com.customer.service.ExecuteCustomerService.initiateExecuteCustomer(..)) 本身完全支持声明异常的方法,问题根源在于通知逻辑未覆盖异常传播路径。通过 try-finally 结构保障监控代码的执行确定性,即可彻底解决“异常方法不触发切点逻辑”的表象问题。











