Spring Boot自动重启后静态ThreadLocal统计SQL执行次数始终为0问题
问题根因
该异常由Spring Boot DevTools热重启的类加载器隔离机制导致:
- DevTools为提升重启效率采用双类加载器设计:
- 基础类加载器:负责加载不会频繁变动的第三方依赖(如P6spy核心组件、JDBC接口等)
- 重启类加载器:负责加载项目自研代码,每次触发热重启都会生成新的实例替换旧加载器
- 首次启动时
P6spyListener被注册到P6spy框架中,而P6spy由基础类加载器持有,热重启后旧的P6spyListener实例不会被销毁,它操作的是旧重启类加载器加载的App类下的静态ThreadLocal变量 - 热重启后新的
RequestInitInterceptor由新的重启类加载器加载,引用的是新App类中的静态ThreadLocal实例,两个ThreadLocal完全独立,因此会出现监听器累加的是旧实例值,拦截器读取的新实例值始终为0的问题。
解决方案
方案1:配置DevTools排除自定义监听器加载(开发环境推荐)
在application.properties中添加配置,将P6spyListener归入基础类加载器的加载范围,热重启时不会重新加载该类,保证引用的ThreadLocal实例统一:
# 排除单个类 spring.devtools.restart.additional-exclude=**/P6spyListener.class # 也可以直接排除整个监听器所在包 # spring.devtools.restart.additional-exclude=com/xxx/listener/**
方案2:关闭DevTools热重启
热重启仅为开发调试功能,不需要时可直接关闭:
spring.devtools.restart.enabled=false
生产环境打包时Spring Boot默认不会引入DevTools依赖,不会出现该问题。
方案3:迁移ThreadLocal到非重启加载范围
将App.count这个静态ThreadLocal变量移动到独立的第三方依赖模块中,这类模块不会被重启类加载器加载,天然保证实例全局唯一。
内容的提问来源于stack exchange,提问作者Cao Vison
相关产品推荐
相关产品推荐

