Spring 6 + Kotlin协程中RequestContextHolder上下文传递丢失问题求助
解决协程切换时RequestContextHolder请求数据丢失问题
1. 替换runBlocking,使用Spring原生协程支持
不要在控制器中手动用runBlocking(Dispatcher.Unconfined)启动协程,直接将控制器方法声明为suspend函数。Spring 6对Kotlin协程有原生支持,会自动为协程绑定请求上下文,确保上下文在协程切换时不丢失:
@RestController class DemoController { @GetMapping("/demo") suspend fun demo(): String { // 直接调用挂起函数,Spring自动管理协程上下文 return auditService.doAudit() } }
2. 自定义协程上下文元素传递RequestAttributes
如果必须手动管理协程上下文,创建自定义协程上下文元素携带RequestAttributes,确保切换线程时上下文不丢失:
// 定义协程上下文元素 data class RequestContextElement(val attributes: RequestAttributes) : CoroutineContext.Element { companion object Key : CoroutineContext.Key<RequestContextElement> override val key: CoroutineContext.Key<*> = Key } // 在需要启动协程的地方注入上下文 val currentRequestAttr = RequestContextHolder.getRequestAttributes() ?: throw IllegalStateException("No request context") withContext(Dispatchers.IO + RequestContextElement(currentRequestAttr)) { // 这里执行的挂起函数可以访问请求上下文 auditorAware.getCurrentAuditor() } // 修改AuditorAware,优先从协程上下文获取RequestAttributes class CustomAuditorAware : AuditorAware<String> { override suspend fun getCurrentAuditor(): Optional<String> { val attributes = RequestContextHolder.getRequestAttributes() ?: coroutineContext[RequestContextElement]?.attributes ?: return Optional.empty() val request = (attributes as ServletRequestAttributes).request val token = request.getHeader("Authorization")?.removePrefix("Bearer ") val email = parseToken(token) // 你的令牌解析逻辑 return Optional.ofNullable(email) } }
3. 避免使用Dispatcher.Unconfined
Dispatcher.Unconfined不会固定线程,协程切换到其他线程时会丢失ThreadLocal存储的RequestContextHolder数据。改用Spring提供的协程调度器(如Dispatchers.IO或通过@Autowired注入的CoroutineDispatcher),Spring会自动处理上下文传递。
4. 排查项目差异点
即使配置表面一致,也要检查:
- 依赖版本:确认两个项目的Spring、Kotlin协程依赖版本完全匹配
- 自动配置:检查是否有项目禁用了Spring协程相关的自动配置
- 拦截器/过滤器:确认请求上下文在进入控制器前已正确加载到RequestContextHolder
5. 替代方案:用RequestScope Bean传递请求信息
绕过RequestContextHolder,创建RequestScope的Bean存储请求信息,Spring会自动处理协程中的Scope传递:
@RequestScope @Component class RequestContextHolderBean { var userEmail: String? = null } // 在拦截器/过滤器中设置邮箱 @Component class TokenParsingFilter(private val requestBean: RequestContextHolderBean) : OncePerRequestFilter() { override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) { val token = request.getHeader("Authorization")?.removePrefix("Bearer ") requestBean.userEmail = parseToken(token) filterChain.doFilter(request, response) } } // 修改AuditorAware直接注入Bean class CustomAuditorAware(private val requestBean: RequestContextHolderBean) : AuditorAware<String> { override suspend fun getCurrentAuditor(): Optional<String> { return Optional.ofNullable(requestBean.userEmail) } }
内容的提问来源于stack exchange,提问作者Leonardo
相关产品推荐
相关产品推荐

