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


相关推荐
道清茗7 分钟前
【MySQL知识点问答题】高级复制技术
数据库·mysql
立莹Sir10 分钟前
Spring Bean生命周期设计思想与源码深度剖析:从表象到本质的全面升级
java·spring·rpc
IT邦德12 分钟前
Oracle备份恢复概览
数据库·oracle
计算机毕业论文辅导16 分钟前
毕业设计避坑指南:工资信息管理系统的设计与实现(Java+SpringBoot实战)
java·spring boot·课程设计
你不是我我19 分钟前
【Java 开发日记】为什么要有 time _wait 状态,服务端这个状态过多是什么原因?
java·网络·php
User_芊芊君子19 分钟前
别再乱用 ArrayList 了!这 4 个隐藏坑,90% 的 Java 开发者都踩过
android·java·数据库
xcLeigh20 分钟前
JAVA项目实战:用飞算 JavaAI 高效开发电商系统核心功能模块
java·ai编程·电商系统·java开发·飞算javaai炫技赛
xcLeigh21 分钟前
IoTDB Java 原生 API 实战:SessionPool 从入门到精通
java·开发语言·数据库·api·iotdb·sessionpool
qq12_81151751521 分钟前
Java Web 影城会员管理系统系统源码-SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0【含文档】
java·前端·mybatis
杜子不疼.22 分钟前
Java 智能体学习避坑指南:3 个常见误区,新手千万别踩,高效少走弯路
java·开发语言·人工智能·学习