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


相关推荐
马克Markorg6 小时前
常见的向量数据库和具有向量数据库能力的数据库
数据库
冷雨夜中漫步6 小时前
Python快速入门(6)——for/if/while语句
开发语言·经验分享·笔记·python
JH30737 小时前
SpringBoot 优雅处理金额格式化:拦截器+自定义注解方案
java·spring boot·spring
m0_736919108 小时前
C++代码风格检查工具
开发语言·c++·算法
Coder_Boy_8 小时前
技术让开发更轻松的底层矛盾
java·大数据·数据库·人工智能·深度学习
2501_944934738 小时前
高职大数据技术专业,CDA和Python认证优先考哪个?
大数据·开发语言·python
helloworldandy8 小时前
使用Pandas进行数据分析:从数据清洗到可视化
jvm·数据库·python
invicinble9 小时前
对tomcat的提供的功能与底层拓扑结构与实现机制的理解
java·tomcat
较真的菜鸟9 小时前
使用ASM和agent监控属性变化
java
黎雁·泠崖9 小时前
【魔法森林冒险】5/14 Allen类(三):任务进度与状态管理
java·开发语言