范围“会话”对于当前线程不活跃; 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指南”中所述 :
DispatcherServlet
,RequestContextListener
和RequestContextFilter
都做同样的事情,即将HTTP请求对象绑定到服务该请求的线程。 这使得请求和会话作用域的bean可以在调用链的下面。
解决scheme1
可以使请求对象可用于其他线程,但是它对系统带来了一些限制,这在所有项目中都是不可行的。 我从一个multithreading的Web应用程序中访问请求范围的bean,得到了这个解决scheme:
我设法解决了这个问题。 我开始使用
SimpleAsyncTaskExecutor
而不是WorkManagerTaskExecutor
/ThreadPoolExecutorFactoryBean
。 好处是SimpleAsyncTaskExecutor
永远不会重新使用线程。 这只是解决scheme的一半。 解决scheme的另一半是使用RequestContextFilter
而不是RequestContextListener
。RequestContextFilter
(以及DispatcherServlet
)有一个threadContextInheritable
属性,它基本上允许子线程inheritance父上下文。
解决scheme2
唯一的其他select是在请求线程中使用session scoped bean。 在我的情况下,这是不可能的,因为:
- 控制器方法用
@Async
注释; - 控制器方法启动使用线程进行并行作业步骤的批处理作业。
根据文件 :
如果你正在访问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>
可以返回以供将来参考)
我的回答是指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>