(记录一下项目中的问题)
异常
java.lang.IllegalStateException: No thread-bound request found
这个错误 java.lang.IllegalStateException: No thread-bound request found 是 Spring 框架中常见的错误,表示你的代码试图访问 HTTP 请求相关的信息,但当前线程并没有绑定任何请求上下文。
通俗解释
就像你去银行办事,但没带身份证(没有请求上下文),柜员(Spring)无法为你办理业务。
常见原因
-
在非Web线程中操作:比如在异步任务、定时任务里调用需要请求的方法
-
缺少配置:忘记配置 RequestContextFilter 或 RequestContextListener
-
测试环境问题:单元测试时没有模拟请求
思路
大部分原因,都是因为主线程的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:本人测试验证,是可以实现的
以上部分的其他解决方案,试过了使用TaskDecorator 、spring异步配置、RequestAttributes、RequestContextTaskDecorator对象传递、都未生效,sprintg+redis+session因公司项目,不方便更改架构未尝试