范围'session'对于当前线程不活动; IllegalStateException:未找到线程绑定请求

时间:2014-01-22 15:11:45

标签: spring wicket

我有一个控制器,我希望每个会话都是唯一的。根据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

9 个答案:

答案 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

中所述
  

DispatcherServletRequestContextListenerRequestContextFilter都做了完全相同的事情,即将HTTP请求对象绑定到为该请求提供服务的Thread。这使得请求和会话范围的bean可以在调用链中进一步使用。

解决方案1 ​​

可以将请求对象提供给其他线程,但它会给系统带来一些限制,这可能无法在所有项目中使用。我从Accessing request scoped beans in a multi-threaded web application得到了这个解决方案:

  

我成功解决了这个问题。我开始使用SimpleAsyncTaskExecutor代替WorkManagerTaskExecutor / ThreadPoolExecutorFactoryBean。好处是SimpleAsyncTaskExecutor永远不会重用线程。这只是解决方案的一半。解决方案的另一半是使用RequestContextFilter而不是RequestContextListenerRequestContextFilter(以及DispatcherServlet)具有threadContextInheritable属性,基本上允许子线程继承父上下文。

解决方案2

唯一的另一种选择是在请求线程中使用会话范围的bean。就我而言,这是不可能的,因为:

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

答案 3 :(得分:7)

作为per documentation

  

如果要访问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)

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

对于此问题,请在上面给出url

检查我的答案

在实际Web请求之外使用请求范围的bean

答案 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,它可以正常工作。