【后端】【Java】《Spring Boot 统一接口耗时统计实践:基于 HandlerInterceptor 的工程级方案》

一步一步讲清楚 :

👉 接口耗时为什么不能写在 Controller 里?

👉 在拦截器里应该怎么"正确、优雅地处理"?


一、为什么不在 Controller 里写耗时代码?

示例代码是这样的:

复制代码
long start = System.currentTimeMillis();
// 业务逻辑
long cost = System.currentTimeMillis() - start;
log.info("接口耗时: {} ms", cost);

❌ 问题有 4 个:

  1. 大量重复代码

    • 每个接口都要写一遍
  2. 业务代码被日志污染

  3. 容易漏写 / 写错

  4. 无法统一统计所有接口

👉 这是典型的横切关注点(Cross-Cutting Concern)

👉 非常适合用:拦截器 / AOP


二、正确方案:在拦截器中统一记录接口耗时

Spring MVC 中,拦截器(HandlerInterceptor)是最合适的位置。


三、拦截器记录耗时的核心思路

复制代码
preHandle     → 记录开始时间
controller    → 业务逻辑
afterCompletion → 计算耗时 + 打日志

四、标准实现方式(推荐写法)

1️⃣ 在 preHandle 中记录开始时间

复制代码
@Component
public class TimeCostInterceptor implements HandlerInterceptor {

    private static final Logger log =
            LoggerFactory.getLogger(TimeCostInterceptor.class);

    private static final String START_TIME = "startTime";

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) {

        request.setAttribute(START_TIME, System.currentTimeMillis());
        return true;
    }

2️⃣ 在 afterCompletion 中计算耗时

复制代码
    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) {

        Long startTime = (Long) request.getAttribute(START_TIME);
        if (startTime == null) {
            return;
        }

        long cost = System.currentTimeMillis() - startTime;

        log.info("接口耗时 | {} {} | {} ms",
                request.getMethod(),
                request.getRequestURI(),
                cost);
    }
}

3️⃣ 注册拦截器

复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new TimeCostInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/static/**");
    }
}

五、最终日志效果(真实可用)

复制代码
2025-01-01 10:00:01.456 INFO
[traceId=9f8a3b7c2d1a4e]
接口耗时 | GET /users/1 | 38 ms

✔ 不侵入 Controller

✔ 所有接口自动统计

✔ 日志格式统一


六、和 TraceId(链路追踪)如何配合?

如果你已经使用了 MDC + TraceId(前一篇博客内容):

复制代码
MDC.put("traceId", traceId);

那么这里的耗时日志 会自动带上 TraceId,无需额外处理。

👉 这就是为什么:

  • TraceFilter

  • TimeCostInterceptor

要一起使用


七、进阶优化(生产环境强烈推荐)

1️⃣ 慢接口告警(非常实用)

复制代码
if (cost > 1000) {
    log.warn("慢接口 | {} {} | {} ms",
            request.getMethod(),
            request.getRequestURI(),
            cost);
}

2️⃣ 区分正常 / 异常请求

复制代码
if (ex != null) {
    log.error("接口异常 | {} {} | {} ms",
            request.getMethod(),
            request.getRequestURI(),
            cost, ex);
}

3️⃣ 只统计 Controller 方法

复制代码
if (!(handler instanceof HandlerMethod)) {
    return;
}

避免静态资源、错误页面干扰统计。


八、拦截器 vs AOP,该选哪个?

场景 推荐
统计接口耗时 ✅ 拦截器
记录方法级别日志 AOP
参数 / 返回值埋点 AOP
接口级统一日志 ✅ 拦截器

👉 HTTP 接口维度 = 拦截器最合适


九、一句话总结(面试 / 实战都能用)

接口耗时属于横切关注点,
应统一在 Spring MVC 拦截器中处理,
避免侵入 Controller 业务逻辑。


相关推荐
小羊没烦恼!3 天前
微服务化的基石——持续集成
java·大数据·word·powerpoint·.net
俊昭喜喜里3 天前
java中的继承和多态的区别
java
小羊没烦恼!3 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
譕痕3 天前
JSONObject与JSONArray封装数据格式区别
java·json
胡写代码3 天前
别再前后端各写一套表单校验了
java·后端
小鱼能吃糖3 天前
缺陷修复总览 · mall电商项目:5类缺陷,1个病根,4个业务域
java·电商
此时不提桶,更待何时3 天前
01-06-A-JVM排查实战详解
java·jvm
伞伞悦读4 天前
【第38期】Python 模块与包详解:import、from、模块搜索路径、包结构和 __init__
开发语言·python
这个DBA有点耶4 天前
MVCC深入:Read View、版本链与快照读——InnoDB并发控制的内核
数据库·mysql·架构
vipxieliang4 天前
ValidX 在 DDD 领域驱动设计中的实践
java·spring boot