You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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,在请求进入时记录时间,后续从上下文直接获取:

  1. 实现拦截器记录请求开始时间
@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;
    }
}
  1. 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private TimingInterceptor timingInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(timingInterceptor);
    }
}
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 17:40:22