You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 14:07:34