我有一个控制器,我希望每个会话都是唯一的。根据spring文档,实现有两个细节:
1。初始网络配置
为了在请求,会话和全局会话级别(Web范围的bean)支持bean的范围,在定义bean之前需要一些小的初始配置。
我已将以下内容添加到web.xml
,如文档中所示:
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
2。作为依赖关系的范围豆
如果要将(例如)HTTP请求作用域bean注入另一个bean,则必须注入AOP代理来代替作用域。
我使用@Scope
注释了bean,提供了proxyMode
,如下所示:
@Controller
@Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class ReportBuilder implements Serializable {
...
...
}
问题
尽管有上述配置,但我得到以下异常:
org.springframework.beans.factory.BeanCreationException:创建名为'scopedTarget.reportBuilder'的bean时出错:当前线程的作用域'session'不活动;考虑为这个bean定义一个范围代理,如果你想从一个单例引用它;嵌套异常是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
我创建了一个可重现的测试用例。这是一个小得多的项目,因此存在差异,但会发生同样的错误。有很多文件,因此我已将其作为tar.gz
上传到megafileupload。
答案 0 :(得分:47)
问题不在于您的Spring注释,而在于您的设计模式。您将不同的范围和线程混合在一起:
单身随处可用,没关系。但是,会话/请求范围在附加到请求的线程之外不可用。
异步作业甚至可以运行请求或会话不再存在,因此不可能使用请求/会话依赖的bean。另外,没有办法知道,如果你在一个单独的线程中运行一个作业,哪个线程是发起者请求(这意味着aop:代理在这种情况下没有帮助)。
我认为您的代码看起来像是要在ReportController,ReportBuilder,UselessTask和ReportPage之间建立契约。有没有办法只使用一个简单的类(POJO)来存储来自UselessTask的数据,并在ReportController或ReportPage中读取它,不再使用ReportBuilder ?
答案 1 :(得分:31)
如果其他人坚持同一点,以下解决了我的问题。
在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>
答案 2 :(得分:29)
我正在回答我自己的问题,因为它可以更好地概述原因和可能的解决方案。我已将奖金授予@Martin,因为他指出了原因。
<强>原因强>
正如@Martin所建议的那样,原因是使用了多个线程。请求对象在这些线程中不可用,如Spring Guide:
中所述
DispatcherServlet
,RequestContextListener
和RequestContextFilter
都做了完全相同的事情,即将HTTP请求对象绑定到为该请求提供服务的Thread。这使得请求和会话范围的bean可以在调用链中进一步使用。
解决方案1
可以将请求对象提供给其他线程,但它会给系统带来一些限制,这可能无法在所有项目中使用。我从Accessing request scoped beans in a multi-threaded web application得到了这个解决方案:
我成功解决了这个问题。我开始使用
SimpleAsyncTaskExecutor
代替WorkManagerTaskExecutor
/ThreadPoolExecutorFactoryBean
。好处是SimpleAsyncTaskExecutor
永远不会重用线程。这只是解决方案的一半。解决方案的另一半是使用RequestContextFilter
而不是RequestContextListener
。RequestContextFilter
(以及DispatcherServlet
)具有threadContextInheritable
属性,基本上允许子线程继承父上下文。
解决方案2
唯一的另一种选择是在请求线程中使用会话范围的bean。就我而言,这是不可能的,因为:
@Async
; 答案 3 :(得分:7)
如果要访问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 Context中才有意义,所以@Controller在代码中没有服务于实际目的。您仍然可以将bean注入到具有会话范围/请求范围的任何位置(您不需要Spring MVC / Controller来在特定范围内注入bean)。
更新:
RequestContextListener仅将请求公开给当前的Thread。
您已经在两个地方自动装配了ReportBuilder
1. ReportPage
- 您可以看到Spring在此处正确注入了“报表”构建器,因为我们仍处于相同的Web线程中。我确实改变了你的代码的顺序,以确保ReportBuilder像这样注入ReportPage。
log.info("ReportBuilder name: {}", reportBuilder.getName());
reportController.getReportData();
我知道日志应该按照你的逻辑进行,只是为了调试目的我添加了。
2. UselessTasklet
- 我们得到了例外,因为这是Spring Batch创建的不同线程,其中RequestContextListener
不公开请求。
你应该有不同的逻辑来创建和注入ReportBuilder
实例到Spring Batch(May Spring Batch Parameters并使用Future<ReportBuilder>
你可以返回以供将来参考)
答案 4 :(得分:2)
答案 5 :(得分:1)
我的回答是指OP描述的一般问题的一个特例,但我会添加它以防万一它可以帮助某人。
使用@EnableOAuth2Sso
时,Spring会在应用程序上下文中放置OAuth2RestTemplate
,并且该组件恰好假设与线程绑定的servlet相关的东西。
我的代码有一个使用自动装配的RestTemplate
的预定异步方法。这不是在DispatcherServlet
内运行,而是Spring正在注入OAuth2RestTemplate
,这会产生OP描述的错误。
解决方案是进行基于名称的注射。在Java配置中:
@Bean
public RestTemplate pingRestTemplate() {
return new RestTemplate();
}
并在使用它的类中:
@Autowired
@Qualifier("pingRestTemplate")
private RestTemplate restTemplate;
现在Spring注入了预期的,无servlet的RestTemplate
。
答案 6 :(得分:1)
我通过在文件中添加以下代码来解决此问题。
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
XML配置-
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
以上我们可以使用Java配置-
@Configuration
@WebListener
public class MyRequestContextListener extends RequestContextListener {
}
How to add a RequestContextListener with no-xml configuration?
我使用的是春季版本5.1.4.RELEASE, 不需要 添加以下pom更改。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>
答案 7 :(得分:0)
您只需要在bean中定义除了prototype之外需要与默认单例范围不同的范围。例如:
<bean id="shoppingCart"
class="com.xxxxx.xxxx.ShoppingCartBean" scope="session">
<aop:scoped-proxy/>
</bean>
答案 8 :(得分:0)
当我在过滤器类上具有@Order
注释时,发生了相同的错误。甚至您也通过HttpSecurity
链添加了过滤器。
删除了@Order
,它可以正常工作。