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 进行递归处理

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

结论

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

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

相关推荐
Java猿_5 小时前
Spring Boot 集成 Sa-Token 实现登录认证与 RBAC 权限控制(实战)
android·spring boot·后端
小王师傅666 小时前
【轻松入门SpringBoot】actuator健康检查(上)
java·spring boot·后端
醒过来摸鱼6 小时前
Java classloader
java·开发语言·python
专注于大数据技术栈6 小时前
java学习--StringBuilder
java·学习
loosenivy6 小时前
企业银行账户归属地查询接口如何用Java调用
java·企业银行账户归属地·企业账户查询接口·企业银行账户查询
码事漫谈6 小时前
C++高并发编程核心技能解析
后端
码事漫谈6 小时前
C++与浏览器交织-从Chrome插件到WebAssembly,开启性能之门
后端
IT 行者7 小时前
Spring Security 6.x 迁移到 7.0 的完整步骤
java·spring·oauth2
JIngJaneIL7 小时前
基于java+ vue农产投入线上管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot
东东的脑洞7 小时前
【面试突击二】JAVA基础知识-volatile、synchronized与ReentrantLock深度对比
java·面试