【后端】【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 业务逻辑。


相关推荐
赵渝强老师2 分钟前
【赵渝强老师】PostgreSQL中表的碎片
数据库·postgresql
华仔啊24 分钟前
Stream 代码越写越难看?JDFrame 让 Java 逻辑回归优雅
java·后端
ray_liang32 分钟前
用六边形架构与整洁架构对比是伪命题?
java·架构
Ray Liang2 小时前
用六边形架构与整洁架构对比是伪命题?
java·python·c#·架构设计
Java水解2 小时前
Java 中间件:Dubbo 服务降级(Mock 机制)
java·后端
全栈老石4 小时前
拆解低代码引擎核心:元数据驱动的"万能表"架构
数据库·低代码
SimonKing6 小时前
OpenCode AI辅助编程,不一样的编程思路,不写一行代码
java·后端·程序员
FastBean6 小时前
Jackson View Extension Spring Boot Starter
java·后端
Seven977 小时前
剑指offer-79、最⻓不含重复字符的⼦字符串
java
皮皮林55117 小时前
Java性能调优黑科技!1行代码实现毫秒级耗时追踪,效率飙升300%!
java