多线程任务中,线程Session传递问题

(记录一下项目中的问题)

异常

java.lang.IllegalStateException: No thread-bound request found

这个错误 java.lang.IllegalStateException: No thread-bound request found 是 Spring 框架中常见的错误,表示你的代码试图访问 HTTP 请求相关的信息,但当前线程并没有绑定任何请求上下文。

通俗解释
就像你去银行办事,但没带身份证(没有请求上下文),柜员(Spring)无法为你办理业务。




常见原因

  1. 在非Web线程中操作:比如在异步任务、定时任务里调用需要请求的方法

  2. 缺少配置:忘记配置 RequestContextFilter 或 RequestContextListener

  3. 测试环境问题:单元测试时没有模拟请求


思路
大部分原因,都是因为主线程的session数据,在子线程session获取时未同步。方案思路都是同步两边线程的session保持一致


解决方案

方案1:添加过滤器配置

@Configuration
public class WebConfig {
    @Bean
    public FilterRegistrationBean<RequestContextFilter> requestContextFilter() {
        FilterRegistrationBean<RequestContextFilter> filter = new FilterRegistrationBean<>();
        filter.setFilter(new RequestContextFilter());
        filter.setOrder(Ordered.HIGHEST_PRECEDENCE); // 设为最高优先级
        return filter;
    }
}




方案2:异步任务手动传递请求

// 在启动异步任务前保存请求
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();

new Thread(() -> {
    try {
        // 把请求绑定到新线程
        RequestContextHolder.setRequestAttributes(attributes);
        // 这里写你的业务代码...
    } finally {
        // 清理请求绑定
        RequestContextHolder.resetRequestAttributes();
    }
}).start();





方案3:使用 Spring Session + Redis(分布式 Session)
[如果必须跨线程修改 Session,可以使用 Spring Session + Redis,让 Session 存储独立于线程]

// application.properties
spring.session.store-type=redis
spring.redis.host=localhost
spring.redis.port=6379

// 子线程代码(可以操作 Session,但必须手动绑定)
executorService.execute(() -> {
    // 模拟请求(必须手动设置 Session ID)
    MockHttpServletRequest mockRequest = new MockHttpServletRequest();
    mockRequest.setSession(new MockHttpSession(sessionId)); // 使用主线程的 Session ID

    // 绑定到当前线程
    RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(mockRequest));

    // 现在可以操作 Session(会同步到 Redis)
    HttpSession session = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getSession();
    session.setAttribute("key", "value"); // 会存储到 Redis

    // 清理
    RequestContextHolder.resetRequestAttributes();
});






方案4:使用 DelegatingRequestContextRunnable(Spring 异步任务)
如果使用 @Async,可以让 Spring 自动传递请求上下文:

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setTaskDecorator(new RequestContextTaskDecorator()); // 关键!自动传递请求
        executor.initialize();
        return executor;
    }
}

// Service 方法
@Async
public void asyncMethod() {
    // 可以直接访问 Session(Spring 会管理请求传递)
    HttpSession session = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                            .getRequest().getSession();
    session.setAttribute("key", "value"); // 可以修改 Session
}






方案5:session手动复制传递

@RequestMapping("/test")
public String testSession(HttpServletRequest request) {
    // 1. 获取并复制必要对象
    HttpSession session = request.getSession();
    String sessionId = session.getId();
    Map<String, Object> sessionData = new HashMap<>();
    Collections.list(session.getAttributeNames())
              .forEach(name -> sessionData.put(name, session.getAttribute(name)));

    // 2. 在子线程重建 Session
    new Thread(() -> {
        try {
            // 模拟新请求
            MockHttpServletRequest mockRequest = new MockHttpServletRequest();
            mockRequest.setSession(new MockHttpSession(sessionId));
            
            // 绑定到当前线程
            RequestContextHolder.setRequestAttributes(
                new ServletRequestAttributes(mockRequest));
            
            // 还原 Session 数据
            sessionData.forEach((k, v) -> 
                mockRequest.getSession().setAttribute(k, v));
            
            // 现在可以安全使用
            System.out.println("Session 值: " + 
                mockRequest.getSession().getAttribute("aaaa"));
        } finally {
            RequestContextHolder.resetRequestAttributes();
        }
    }).start();
    
    return "success";
}



  @RequestMapping("/testSession1")
    public void testSession1(HttpServletRequest request, HttpServletResponse response){
        HttpSession session = request.getSession();
        session.setAttribute("aaaa","张三");
    }

    public void printSession(){
        System.out.println("内部");
        System.out.println("进入打印session");
        Object aaaa = session.getAttribute("aaaa");
        System.out.println(aaaa.toString());
    }

总结

方案5:本人测试验证,是可以实现的

以上部分的其他解决方案,试过了使用TaskDecoratorspring异步配置RequestAttributesRequestContextTaskDecorator对象传递、都未生效,sprintg+redis+session因公司项目,不方便更改架构未尝试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值