范围“会话”对于当前线程不活跃; IllegalStateException:找不到线程绑定的请求

我有一个控制器,我想每个会话都是唯一的。 根据春季文档,有两个细节执行:

1.初始网页configuration

为了在请求,会话和全局会话级别(Web范围的bean)上支持bean的范围界定,在定义bean之前需要一些次要的初始configuration。

我已经将以下内容添加到我的web.xml ,如文档中所示:

 <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> 

2.作为依赖关系的作用域bean

如果您想要将HTTP请求范围的bean注入(例如)另一个bean,则必须注入一个AOP代理来代替范围的bean。

我用@Scope注释了bean,提供了proxyMode ,如下所示:

 @Controller @Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS) public class ReportBuilder implements Serializable { ... ... } 

问题

尽pipe有了上面的configuration,但我得到了以下例外:

org.springframework.beans.factory.BeanCreationException:创build名为'scopedTarget.reportBuilder'的bean时出错:作用域'session'对当前线程不活跃; 考虑为这个bean定义一个范围代理,如果你打算从一个单例中引用它; 嵌套的exception是java.lang.IllegalStateException:没有发现线程绑定的请求:是否引用实际Web请求之外的请求属性,或者在原始接收线程之外处理请求? 如果您实际上在Web请求中运行并仍然收到此消息,那么您的代码可能在DispatcherServlet / DispatcherPortlet之外运行:在这种情况下,请使用RequestContextListener或RequestContextFilter来公开当前请求。

更新1

以下是我的组件扫描。 我在web.xml有以下内容:

 <context-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value> </context-param> <context-param> <param-name>contextConfigLocation</param-name> <param-value>org.example.AppConfig</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> 

以下在AppConfig.java

 @Configuration @EnableAsync @EnableCaching @ComponentScan("org.example") @ImportResource("classpath:applicationContext.xml") public class AppConfig implements AsyncConfigurer { ... ... } 

更新2

我创build了一个可重复的testing用例。 这是一个小得多的项目,所以有差异,但同样的错误发生。 有相当多的文件,所以我已经把它作为tar.gz上传到megafileupload 。

问题不在于你的Spring注解,而在于你的devise模式。 你将不同的范围和线程混合在一起:

  • 独生子
  • 会话(或请求)
  • 线程池的作业

单身人士在任何地方都可以,没关系。 但是,会话/请求范围在连接到请求的线程之外不可用。

即使请求或会话不再存在,asynchronous作业也可以运行,所以不可能使用请求/会话相关的bean。 也没有办法知道,如果你正在一个单独的线程中运行一个工作,哪个线程是发起者的请求(这意味着aop:代理在这种情况下是没有帮助的)。


我认为你的代码看起来像你想在ReportController,ReportBuilder,UselessTask和ReportPage之间build立一个契约 。 有没有办法只使用一个简单的类(POJO)来存储来自UselessTask的数据,并将其读入ReportController或ReportPage中,而不再使用ReportBuilder

如果其他人坚持同一点,下面解决了我的问题。

在web.xml中

  <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> 

在会话组件中

 @Component @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) 

在pom.xml中

  <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.1</version> </dependency> 

我正在回答我自己的问题,因为它提供了一个更好的原因和可能的解决scheme的概述。 我已经把奖金给了马丁,因为他指出了原因。

原因

正如@Martinbuild议的原因是使用多个线程。 请求对象在这些线程中不可用,如“ Spring指南”中所述 :

DispatcherServletRequestContextListenerRequestContextFilter都做同样的事情,即将HTTP请求对象绑定到服务该请求的线程。 这使得请求和会话作用域的bean可以在调用链的下面。

解决scheme1

可以使请求对象可用于其他线程,但是它对系统带来了一些限制,这在所有项目中都是不可行的。 我从一个multithreading的Web应用程序中访问请求范围的bean,得到了这个解决scheme:

我设法解决了这个问题。 我开始使用SimpleAsyncTaskExecutor而不是WorkManagerTaskExecutor / ThreadPoolExecutorFactoryBean 。 好处是SimpleAsyncTaskExecutor永远不会重新使用线程。 这只是解决scheme的一半。 解决scheme的另一半是使用RequestContextFilter而不是RequestContextListenerRequestContextFilter (以及DispatcherServlet )有一个threadContextInheritable属性,它基本上允许子线程inheritance父上下文。

解决scheme2

唯一的其他select是在请求线程中使用session scoped bean。 在我的情况下,这是不可能的,因为:

  1. 控制器方法用@Async注释;
  2. 控制器方法启动使用线程进行并行作业步骤的批处理作业。

根据文件 :

如果你正在访问Spring Web MVC中的范围化的bean,也就是在由Spring DispatcherServlet或DispatcherPortlet处理的请求中,那么不需要特别的设置:DispatcherServlet和DispatcherPortlet已经公开了所有相关的状态。

如果你在Spring MVC之外运行(不是由DispatchServlet处理),你必须使用RequestContextListener不仅仅是ContextLoaderListener

在web.xml中添加以下内容

  <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> 

这将为Spring提供会话,以便维护该范围内的bean

更新:根据其他答案, @Controller只有在Spring MVC上下文中才合理,所以@Controller在代码中没有实际用途。 仍然可以将bean注入任何具有会话范围/请求范围的地方(您不需要Spring MVC / Controller就可以在特定范围内注入Bean)。

更新: RequestContextListener只将请求公开给当前的线程。
你有两个地方的自动assemblyReportBuilder

1. ReportPage – 你可以看到Spring在这里正确的注入了报表生成器,因为我们仍然在同一个web线程中。 我没有改变你的代码的顺序,以确保ReportBuilder像这样注入ReportPage。

 log.info("ReportBuilder name: {}", reportBuilder.getName()); reportController.getReportData(); 

我知道日志应该遵循你的逻辑,只是为了debugging目的,我补充说。

2. UselessTasklet – 我们得到了exception,这是因为这是由Spring Batch创build的不同线程,其中Request没有被RequestContextListener公开。

您应该有不同的逻辑来创buildReportBuilder实例并将其注入到Spring Batch中(可以使用Spring的批处理参数并使用Future<ReportBuilder>可以返回以供将来参考)

https://stackoverflow.com/a/30640097/2569475

对于这个问题检查我的答案在上面给定的url

在实际Web请求之外使用请求作用域bean

我的回答是指OP描述的一般问题的一个特例,但为了防止有人出门,我会加上它。

当使用@EnableOAuth2Sso ,Spring OAuth2RestTemplate在应用程序上下文中放置一个OAuth2RestTemplate ,并且这个组件正好假设线程绑定的servlet相关的东西。

我的代码有一个使用自动assemblyRestTemplate的预定asynchronous方法。 这不在DispatcherServlet运行,但是Spring注入了OAuth2RestTemplate描述的OAuth2RestTemplate ,它产生了错误。

解决scheme是做基于名称的注射。 在Javaconfiguration中:

 @Bean public RestTemplate pingRestTemplate() { return new RestTemplate(); } 

并在使用它的类中:

 @Autowired @Qualifier("pingRestTemplate") private RestTemplate restTemplate; 

现在Spring将注入一个无servlet的RestTemplate

你只需要在你的bean中定义你需要一个不同于默认的singleton scope的范围,除了prototype。 例如:

 <bean id="shoppingCart" class="com.xxxxx.xxxx.ShoppingCartBean" scope="session"> <aop:scoped-proxy/> </bean> 
    Interesting Posts