Spring Boot 里怎么统计接口参数和耗时并打印日志

文章目录

    • [一、 三种方案的代码演练](#一、 三种方案的代码演练)
      • [1. AOP(Aspect 面向切面)方案 ------ 针对 Controller 方法(最推荐)](#1. AOP(Aspect 面向切面)方案 —— 针对 Controller 方法(最推荐))
      • [2. Interceptor(Spring 拦截器)方案 ------ 针对 Spring MVC 链路](#2. Interceptor(Spring 拦截器)方案 —— 针对 Spring MVC 链路)
      • [3. Filter(Servlet 过滤器)方案 ------ 针对最外层 HTTP 请求](#3. Filter(Servlet 过滤器)方案 —— 针对最外层 HTTP 请求)
    • [二、 面试官角度:三种方案的本质区别(核心考点)](#二、 面试官角度:三种方案的本质区别(核心考点))
    • [三、 复盘:你在面试中的回答到底对不对?](#三、 复盘:你在面试中的回答到底对不对?)
      • [1. 得分点(说得对的地方)](#1. 得分点(说得对的地方))
    • [四、 下一次面试,如何给出"满分回答"?](#四、 下一次面试,如何给出“满分回答”?)

根据你提供的面试复盘音频记录,面试官在 18:29 问到了你这个问题:"如果说在代码中,如果要实现接口的参数,耗时,接口花了多长时间,日志的打印,你会去怎么做?"

首先给你打个气,你提到了 AOP(面向切面编程) 并在前后获取时间戳相减,这个核心思路是完全正确的! 但是,你在细节表达上有些含糊(比如提到了"ConcurrentMedia"这个不存在的词 ,可能是想说 System.currentTimeMillis() 或者是 ProceedingJoinPoint),而且没有展开具体的落地方案。面试官最后也建议你:"可以调整一下自己的思路,写出来比说要好一点" 。

为了帮你完美复盘并准备接下来的面试,下面我用 Spring Boot 的三种主流拦截机制(AOP、Interceptor、Filter) 分别为你演示怎么实现这个功能,并客观评估你当时的面试回答。


一、 三种方案的代码演练

在 Spring Boot 中,统计接口参数和耗时通常有以下三种做法。我们由浅入深,从你提到的 AOP 开始:

1. AOP(Aspect 面向切面)方案 ------ 针对 Controller 方法(最推荐)

AOP 能够直接获取到 Spring 解析后的 Java 对象入参,最适合用来做复杂的日志审计和耗时统计。

java 复制代码
@Aspect
@Component
@Slf4j
public class LogAspect {

    @Resource
    private ObjectMapper objectMapper; // 用于序列化参数

    // 拦截所有 controller 包下的方法
    @Pointcut("execution(public * com.example.demo.controller..*.*(..))")
    public void controllerLog() {}

    @Around("controllerLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis(); // 记录开始时间

        // 获取方法名
        String methodName = joinPoint.getSignature().getName();
        // 获取入参对象数组
        Object[] args = joinPoint.getArgs();
        
        log.info("【AOP日志】进入方法: {}, 参数: {}", methodName, objectMapper.writeValueAsString(args));

        // 执行核心业务方法
        Object result = joinPoint.proceed();

        long executionTime = System.currentTimeMillis() - startTime; // 计算耗时
        log.info("【AOP日志】退出方法: {}, 耗时: {}ms", methodName, executionTime);

        return result;
    }
}

2. Interceptor(Spring 拦截器)方案 ------ 针对 Spring MVC 链路

拦截器属于 Spring MVC 级别,可以在请求到达 Controller 之前和之后进行拦截,非常适合配合 ThreadLocal 统计耗时。

java 复制代码
@Component
@Slf4j
public class LogInterceptor implements HandlerInterceptor {

    // 使用 ThreadLocal 保证多线程环境下线程安全地记录各自请求的开始时间
    private static final ThreadLocal<Long> TIME_THREAD_LOCAL = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        long startTime = System.currentTimeMillis();
        TIME_THREAD_LOCAL.set(startTime); // 存入当前线程
        
        // 打印基础请求参数(URL参数)
        log.info("【拦截器】请求 URI: {}, Method: {}, 参数: {}", request.getRequestURI(), request.getMethod(), request.getParameterMap());
        return true; // 放行
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        Long startTime = TIME_THREAD_LOCAL.get();
        if (startTime != null) {
            long endTime = System.currentTimeMillis();
            log.info("【拦截器】请求 URI: {} 处理完成,总耗时: {}ms", request.getRequestURI(), (endTime - startTime));
            TIME_THREAD_LOCAL.remove(); // 极其重要:用完必须释放,防止内存泄漏
        }
    }
}

3. Filter(Servlet 过滤器)方案 ------ 针对最外层 HTTP 请求

过滤器属于标准 Servlet 容器级别(如 Tomcat),是全系统最外层的防线。它的生命周期甚至比 Spring 还要早。

java 复制代码
@Component
@WebFilter(urlPatterns = "/*")
@Slf4j
public class LogFilter implements Filter {

    @Override
    public void doFilter(ServletRequest context, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) context;
        long startTime = System.currentTimeMillis();

        log.info("【过滤器】开始拦截请求: {}", request.getRequestURI());

        // 放行,让请求继续往后走(走拦截器、Controller等)
        chain.doFilter(context, response);

        long executionTime = System.currentTimeMillis() - startTime;
        log.info("【过滤器】请求: {} 结束,耗时: {}ms", request.getRequestURI(), executionTime);
    }
}

二、 面试官角度:三种方案的本质区别(核心考点)

如果你能在面试中主动说出下面这张表对比,面试官会觉得你的计算机网络和 Spring 底层功底极其扎实:

维度 Filter (过滤器) Interceptor (拦截器) AOP (切面)
所属框架 Servlet 容器规范(Tomcat) Spring MVC 框架 Spring Core 框架
拦截边界 HTTP 请求的最外层入口 进入 SpringMVC 但在 Controller 之前 具体的某一个方法(Method)
获取入参的便利度 极其困难 。只能拿到原始 Http 字节流,如果读了 RequestBody 后面业务就读不到了。 中等 。能拿到 request.getParameter(),但同样存在 Body 只能读一次的问题。 极其轻松 。Spring 已经帮我们把参数反序列化成了 Java 对象joinPoint.getArgs())。
核心应用场景 全局跨域(CORS)、安全过滤(XSS防注入)、全局解密。 权限校验(鉴权)、TraceId 注入。 性能监控(统计耗时)、事务控制、操作审计日志。

三、 复盘:你在面试中的回答到底对不对?

针对你当时的现场表现,我为你做一个客观的"诊断报告":

1. 得分点(说得对的地方)

思路切中要害 :你第一时间回答了"可以用 AOP 去做" ,并且提到了 around(环绕通知),还知道是在方法执行前后分别获取时间做减法 。在逻辑闭环上是完全成立的。

拓展知识面广 :你顺带提到了可以使用 Arthas(阿尔萨斯) ,说明你具备线上排查慢接口的实际工具经验。

  1. 失分点(导致你最后"心虚"和减分的地方)

专业术语口误(致命伤) :你在描述时间戳或者连接点时,连续说了两次 "获取一个 ConcurrentMedia" 。Java 和 Spring 里完全没有这个类(你可能脑子里想的是 ConcurrentHashMap 或者 System.currentTimeMillis() 的杂交语)。这会让面试官觉得你只是背了概念,没有真正写过代码。

缺乏落地方案 :当面试官追问"有哪几种方式"以及"你会拦截哪一层"时 ,你的语言开始细碎重复("就是那回事嘛......" 这种口头禅容易显得不自信) 。你没有清晰地划分出 Filter -> Interceptor -> AOP 这一条清晰的拦截链路。


四、 下一次面试,如何给出"满分回答"?

如果下次再被问到:"Spring Boot 里怎么统计接口参数和耗时并打印日志?"

🗣️ 你应该这样优雅地回答:

"在 Spring Boot 中,通常有 Filter、Interceptor 和 AOP 三种方式可以拦截请求并统计耗时。

在实际生产中,对于'统计接口入参和耗时'这个特定需求,我个人最推荐使用 Spring AOP 结合 @Around 环绕通知来实现

因为如果使用 Filter 或 Interceptor,由于 HTTP 请求的 RequestBody 输入流只能读取一次,如果我们为了打印日志强制去读取它,就会导致后续的 Controller 无法正常解析参数(即流关闭异常)。而使用 AOP ,由于它切入的是方法级别,我们能通过 ProceedingJoinPoint.getArgs() 直接拿到 Spring 已经反序列化好的 Java 对象入参 ,对核心业务完全做到零侵入。
我的落地做法是:

在环绕通知中,先通过 System.currentTimeMillis() 记录当前时间,并利用 objectMapper 将入参对象转为 JSON 字符串打印出来(注意要过滤掉类似 HttpServletRequest 这种无法序列化的内置对象)。接着调用 joinPoint.proceed() 执行业务,最后再次获取时间戳相减,算出方法总耗时。

此外,为了方便线上监控,我通常会在代码里设定一个慢响应阈值 (比如 3 秒)。一旦耗时超过 3 秒,日志级别会从 INFO 提升为 WARN,并在日志头部打上 【接口慢响应警告】 标签,方便后续我们在 ELK 日志平台中通过脚本直接拉取和报警。"

相关推荐
阿维的博客日记20 小时前
Hippo4j 线程池监控平台部署手册
java·spring boot·后端
万少21 小时前
AtomCode开发微信小程序《谁去呀》 全流程
前端·javascript·后端
GetcharZp21 小时前
Epic、暴雪都在用的 C++ 界面利器:Dear ImGui 零基础全景指南
后端
C+++Python1 天前
详细介绍一下Java泛型的通配符
java·windows·python
pixcarp1 天前
知识库系统的内容资产闭环怎么设计
服务器·数据库·后端·golang
红尘散仙1 天前
别再手动录屏了:用 VHS 给终端应用生成会动的文档素材
后端·rust
JosieBook1 天前
【数据库】时序预测能力的分级进化:TimechoAI如何让每一类用户都能精准预见未来
java·开发语言·数据库
一生了无挂1 天前
Java处理JSON技巧教学(从基础到高阶实战全覆盖)
java·开发语言·json
李白的天不白1 天前
使用 SmartAdmin 进行前后端开发
java·前端
swordbob1 天前
Spring 单例 Bean 是线程安全的吗?
java·开发语言