如何在Spring ExceptionHandler中记录调用方法的执行时间
问题描述
我正在重构应用并打算使用ExceptionHandler,已实现如下方法:
@org.springframework.web.bind.annotation.ExceptionHandler(Exception.class) public final ResponseEntity<SomeClass> handleException(Exception e) { // do something }
现在希望在ExceptionHandler被调用时,也记录调用方法的执行时间。我原先的实现方式为:
public void doSomething() { Instant start = Instant.now(); try { // do some fancy code stuff } catch (Exception e) { logTime(Duration.between(start, Instant.now()).toMillis()); } }
请问如何将startTime信息传入ExceptionHandler方法?或者是否有更优的实现方案?我已查看Exception对象结构,但未找到所需信息。
可行方案
方案一:自定义异常携带 startTime
创建自定义异常类,将startTime作为属性存入,业务代码中抛出该异常,在ExceptionHandler中读取属性计算耗时:
// 自定义异常类 public class TimedException extends RuntimeException { private final Instant startTime; public TimedException(String message, Throwable cause, Instant startTime) { super(message, cause); this.startTime = startTime; } public Instant getStartTime() { return startTime; } }
业务代码修改:
public void doSomething() { Instant start = Instant.now(); try { // do some fancy code stuff throw new RuntimeException("业务异常"); // 示例异常 } catch (Exception e) { throw new TimedException(e.getMessage(), e, start); } }
更新ExceptionHandler:
@ExceptionHandler(TimedException.class) public final ResponseEntity<SomeClass> handleTimedException(TimedException e) { long cost = Duration.between(e.getStartTime(), Instant.now()).toMillis(); logTime(cost); // 原有异常处理逻辑 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(...); } // 保留通用异常处理,兼容未携带时间的异常 @ExceptionHandler(Exception.class) public final ResponseEntity<SomeClass> handleException(Exception e) { // 原有处理逻辑 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(...); }
方案二:利用请求上下文存储 startTime
借助Spring的RequestContextHolder,在请求进入时记录时间,后续从上下文直接获取:
- 实现拦截器记录请求开始时间
@Component public class TimingInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { request.setAttribute("startTime", Instant.now()); return true; } }
- 注册拦截器
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private TimingInterceptor timingInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(timingInterceptor); } }
- 在
ExceptionHandler中读取时间
@ExceptionHandler(Exception.class) public final ResponseEntity<SomeClass> handleException(Exception e, HttpServletRequest request) { Instant startTime = (Instant) request.getAttribute("startTime"); if (startTime != null) { long cost = Duration.between(startTime, Instant.now()).toMillis(); logTime(cost); } // 原有异常处理逻辑 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(...); }
方案三:AOP统一统计耗时(最优推荐)
通过AOP环绕通知对目标方法做耗时统计,异常时记录后再抛出给ExceptionHandler处理,无需修改业务代码:
@Aspect @Component public class MethodTimingAspect { private static final Logger logger = LoggerFactory.getLogger(MethodTimingAspect.class); // 定义切点,匹配需要统计的方法(示例为指定包下所有方法) @Pointcut("execution(* com.yourpackage..*.*(..))") public void targetMethods() {} @Around("targetMethods()") public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable { Instant start = Instant.now(); Object result; try { result = joinPoint.proceed(); } catch (Exception e) { long cost = Duration.between(start, Instant.now()).toMillis(); logTime(cost); throw e; // 抛出异常交由ExceptionHandler处理 } // 若需要记录正常执行的耗时,可在此添加逻辑 return result; } }
内容的提问来源于stack exchange,提问作者Marco Florio
相关产品推荐
相关产品推荐

