如何在静态方法中使用Spring Bean?请求域Bean静态调用为空问题
你的代码出现静态调用HeaderHolder.get().anonymousUserId为null的问题,核心原因有两点:
全局单例
instance变量的并发冲突HeaderHolder的companion object里的instance是全局共享的静态变量,每个请求创建的HeaderHolder实例都会通过afterPropertiesSet覆盖这个变量。并发场景下,后到达的请求会覆盖先请求的instance引用,导致先请求的静态调用拿到的是其他请求的实例,而该实例的anonymousUserId可能还未赋值或为空。Spring Request Scope代理与静态存储的不匹配
你定义的HeaderHolder是request作用域的Bean,Spring会为它生成CGLIB代理对象。当HeaderInterceptor调用代理对象的anonymousUserId属性时,代理会从当前请求上下文获取实际的HeaderHolder实例,但全局instance变量无法保证和当前请求的实际实例一一对应,极端情况下可能出现当前请求的实例还未完成instance赋值,静态调用就已经执行的情况。
要实现并发安全且能在静态方法中直接获取请求header值的需求,最可靠的方式是用ThreadLocal存储每个请求的独立数据(Spring的request scope底层也是基于ThreadLocal实现的)。
1. 重构HeaderHolder类
去掉Spring Bean相关依赖,改用ThreadLocal存储数据,确保每个线程(请求)拥有独立副本:
object HeaderHolder { private val anonymousUserIdThreadLocal = ThreadLocal<String?>() // 设置请求头中的用户ID fun setAnonymousUserId(userId: String?) { anonymousUserIdThreadLocal.set(userId) } // 静态方法获取用户ID fun getAnonymousUserId(): String? { return anonymousUserIdThreadLocal.get() } // 请求结束后清理ThreadLocal,避免内存泄漏 fun clear() { anonymousUserIdThreadLocal.remove() } }
2. 修改HeaderInterceptor
在拦截器中设置ThreadLocal的值,并在请求完成后清理:
class HeaderInterceptor : HandlerInterceptor { @Throws(Exception::class) override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean { val anonymousUserId = request.getHeader(ANONYMOUS_USER_ID_HEADER_NAME) HeaderHolder.setAnonymousUserId(anonymousUserId) return true } // 必须清理ThreadLocal,否则线程池复用会导致数据串用 override fun afterCompletion(request: HttpServletRequest, response: HttpServletResponse, handler: Any, ex: Exception?) { HeaderHolder.clear() } companion object { const val ANONYMOUS_USER_ID_HEADER_NAME = "anonymous_user_id" } }
3. 更新配置类
不再需要request作用域的HeaderHolder Bean,简化配置:
@Configuration class HeaderInterceptorConfig : WebMvcConfigurer { override fun addInterceptors(registry: InterceptorRegistry) { registry.addInterceptor(HeaderInterceptor()) } }
4. Controller中调用静态方法
@RestController @RequestMapping("/v1/") class MyController { @GetMapping() fun getField() { println("static method get: ${HeaderHolder.getAnonymousUserId()}") // 正常获取值 } }
方案优势
- 并发安全:
ThreadLocal为每个线程维护独立变量副本,不同请求数据完全隔离,无串值问题。 - 静态调用友好:直接通过
object类的静态方法访问,无需依赖Spring注入,符合需求。 - 避免内存泄漏:在
afterCompletion中清理ThreadLocal,防止线程池复用旧线程时残留之前请求的数据。
内容的提问来源于stack exchange,提问作者NeverSleeps

