springboot 项目某个接口响应特别慢排查

问题现象

接口请求日志显示耗时仅为几十毫秒,但通过Postman发起相同请求时,实际耗时却高达7秒多。

bash 复制代码
[2025-12-02 09:06:27.344]-[INFO]-[http-nio-3012-exec-3]-[logId:6600b2f0-8d68-4c2c-a7b5-bce11a42e140]-[com.xxx.interceptor.ControllerInterceptor.info:63] 调用 接口://task/retry Class:com.xxx.xxx.xxx.controller.TaskController,Method:taskRetry,Request:101   
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@63bd0ef2] was not registered for synchronization because synchronization is not active
JDBC Connection [com.mysql.jdbc.JDBC4Connection@3448c914] will not be xxxd by Spring
==>  Preparing: select id, task_code, task_name, task_desc, task_execute_time, task_over_time, task_timer, file_path, status, task_type, create_name, update_name, create_date, update_date from r_task where id = ? and status = ? 
==> Parameters: 101(Integer), 4(Byte)
<==      Total: 0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@63bd0ef2]
[2025-12-02 09:06:27.354]-[WARN]-[http-nio-3012-exec-3]-[logId:6600b2f0-8d68-4c2c-a7b5-bce11a42e140]-[com.xxx..xxx.service.impl.RetryTaskServiceImpl.taskRetry:50]-任务重试任务查询为空,id:101
[2025-12-02 09:06:27.355]-[INFO]-[http-nio-3012-exec-3]-[logId:6600b2f0-8d68-4c2c-a7b5-bce11a42e140]-[com.xxx.interceptor.ControllerInterceptor.logReturn:81]-Class:com.xxx..xxx.controller.TaskController,Method:taskRetry,Response:{"code":"999","msg":"任务重试失败"}

postman请求示例

问题排查

先用arthas的 trace命令 确定哪块执行比较耗时

bash 复制代码
    `---[6792.636ms] org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor:intercept()
        +---[0.0061ms] org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor:getTarget() #655
        +---[0.005ms] org.springframework.aop.framework.AdvisedSupport:getInterceptorsAndDynamicInterceptionAdvice() #659
        +---[0.0079ms] org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation:<init>() #673
        +---[6792.5681ms] org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation:proceed() #673
        |   `---[10.2326ms] org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor:intercept()
        |       +---[0.0044ms] org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor:getTarget() #655
        |       +---[0.0047ms] org.springframework.aop.framework.AdvisedSupport:getInterceptorsAndDynamicInterceptionAdvice() #659
        |       +---[0.0092ms] org.springframework.aop.framework.AopProxyUtils:adaptArgumentsIfNecessary() #668
        |       +---[10.1483ms] org.springframework.cglib.proxy.MethodProxy:invoke() #669
        |       +---[0.0034ms] org.springframework.aop.framework.CglibAopProxy:access$000() #675
        |       `---[0.0031ms] org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor:releaseTarget() #680
        +---[0.0034ms] org.springframework.aop.framework.CglibAopProxy:access$000() #675
        `---[0.0025ms] org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor:releaseTarget() #680

经过断点调试,程序最终进入了自定义的ControllerInterceptor中的logBefore方法。该方法的主要逻辑相对简单,主要用于打印请求日志并对输入参数进行脱敏处理。问题出在负责参数脱敏的SensitiveInfoSerialize.getJson方法上。

通过断点跟踪发现,程序是在MethodBeforeAdviceInterceptor中执行this.advice.before这行代码时进入自定义ControllerInterceptor的。

java 复制代码
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable {
    private MethodBeforeAdvice advice;

    public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
        Assert.notNull(advice, "Advice must not be null");
        this.advice = advice;
    }

    public Object invoke(MethodInvocation mi) throws Throwable {
        this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
        return mi.proceed();
    }
}

ControllerInterceptor 的logBefore

java 复制代码
 /**
     * /**
     * 记录输入日志
     *
     * @param joinPoint
     */
    @Before(value = "controllerService()")
    public void logBefore(JoinPoint joinPoint) {
        LogUtil.bindLogId(UUID.randomUUID().toString());
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        if (ArrayUtils.isNotEmpty(args)) {
            LogHelper.info(logger, "调用", "接口:" + logTool.getServiceKey(), "Class:" + className
                    + ",Method:" + methodName + ",Request:" + SensitiveInfoSerialize.getJson(args[0]), "", "", "");
        }
    }

这段代码的核心逻辑是:

  1. 遍历 Java Bean 的所有字段
  2. 识别带有 SensitiveInfo 注解的 String 类型字段
  3. 对这些字段执行脱敏处理
  4. 对嵌套对象、数组、集合和 Map 进行递归处理

需要注意的是,这是一个内部工具类,虽然具体实现不便展示,但实际使用中存在一些需要注意的问题。

结论

根本原因是对参数进行递归的时候没有排除基本数据类型,导致对基本数据类型的字段进行递归导致耗时很长。

所以使用递归时要特别注意退出条件,不然可能会导致耗时很长甚至栈溢出。

相关推荐
陈随易25 分钟前
Postman 被曝自动往 Claude Code 和 Codex 装插件
前端·后端·程序员
AI你一生一世2 小时前
不止于时序存储:DolphinDB 如何成为 AI 时代的工业智能底座
java·人工智能·struts·时序数据库·工业智能·dolphindb·ai基础设施
云-缩影2 小时前
拆解 AI 图像生成应用的后端架构:ECS、OSS 和大模型 API 怎么配合
java·spring boot·后端
瑞克-桑切斯2 小时前
7、MySQL 慢查询排查与优化实战指南
java·服务器·数据库
考虑考虑3 小时前
Posrgresql恢复.backup文件
数据库·后端·postgresql
无奈何杨4 小时前
风控决策系统决策流,策略模式,权重、顺序、投票、最坏
后端
AI开源软件4 小时前
SpringBoot3+Vue3 财务管理系统如何设计:从业务单据到总账的模块划分
spring boot·vue3·领域建模·会计凭证·ruoyi office·业财协同·财务系统设计
Patrick_Wilson5 小时前
Go 为什么需要 defer?从函数怎么收尾说起
后端·go·编程语言
苏三说技术5 小时前
DeepSeek Harness入驻B站,评论区被干成了大型插件集市!
后端
橙序员小站5 小时前
我一句话让 AI 把公众号工作台搭完了:部署实测记录
后端