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


相关推荐
Evan芙4 小时前
php多版本编译安装
开发语言·php
m0_740043734 小时前
SpringBoot03-Mybatis框架入门
java·数据库·spring boot·sql·spring·mybatis
黑客思维者4 小时前
Python modbus-tk在配电物联网边缘网关的应用
开发语言·python·物联网
Cigaretter74 小时前
Day 30 类的定义与方法
开发语言·python
默 语4 小时前
用Java撸一个AI聊天机器人:从零到一的踩坑实录
java·人工智能·spring·ai·机器人·spring ai
Logic1014 小时前
《数据库运维》 郭文明 实验2 MySQL数据库对象管理核心操作与思路解析
运维·数据库·mysql·学习笔记·计算机网络技术·形考作业·国家开放大学
脸大是真的好~4 小时前
黑马消息队列-rabbitMQ2-生产者重连机制-生产者确认机制-数据持久化-LazyQueue-消费者确认机制-失败重试机制-重试耗尽告警手动处理-
java·缓存·中间件
郝学胜-神的一滴4 小时前
Separate Buffer、InterleavedBuffer 策略与 OpenGL VAO 深度解析
开发语言·c++·程序人生·算法·游戏程序·图形渲染