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

问题描述 投票:47回答:8

我有一个控制器,我希望每个会话都是唯一的。根据spring文档,实现有两个细节:

1.初始Web配置

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

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

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

2. Scoped bean作为依赖项

如果要将(例如)HTTP请求作用域bean注入另一个bean,则必须注入AOP代理来代替作用域bean。

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

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

问题

尽管有上述配置,但我得到以下异常:

org.springframework.beans.factory.BeanCreationException:创建名为'scopedTarget.reportBuilder'的bean时出错:Scope'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

spring wicket
8个回答
49
投票

问题不在于您的Spring注释,而在于您的设计模式。您将不同的范围和线程混合在一起:

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

单身随处可用,没关系。但是,会话/请求范围在附加到请求的线程之外不可用。

异步作业甚至可以运行请求或会话不再存在,因此不可能使用请求/会话依赖bean。另外,没有办法知道,如果你在一个单独的线程中运行一个作业,哪个线程是发起者请求(这意味着aop:代理在这种情况下没有帮助)。


我认为您的代码看起来像是要在ReportController,ReportBuilder,UselessTask和ReportPage之间签订合同。有没有办法只使用一个简单的类(POJO)来存储来自UselessTask的数据,并在ReportController或ReportPage中读取它,不再使用ReportBuilder?


31
投票

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

在web.xml中

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

在Session组件中

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

在pom.xml中

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

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. 控制器方法启动批处理作业,该作业使用线程进行并行作业步骤。

7
投票

作为per documentation

如果要访问Spring Web MVC中的作用域bean,即在Spring DispatcherServlet或DispatcherPortlet处理的请求中,则无需进行特殊设置:DispatcherServlet和DispatcherPortlet已公开所有相关状态。

如果你在Spring MVC之外运行(不是由DispatcherServlet处理)你必须使用qazxsw poi而不仅仅是qazxsw poi。

在web.xml中添加以下内容

RequestContextListener

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

更新:根据其他答案,ContextLoaderListener只有在Spring MVC Context中才有意义,所以@Controller在你的代码中没有服务于实际目的。您仍然可以将bean注入任何具有会话范围/请求范围的位置(您不需要Spring MVC / Controller来在特定范围内注入bean)。

更新: <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> 仅将请求公开给当前线程。 您已经在两个地方自动装配了ReportBuilder 1. @Controller - 你可以看到Spring在这里正确地注入了Report构建器,因为我们仍然在Same web Thread中。我确实改变了你的代码的顺序,以确保ReportBuilder像这样注入ReportPage。

RequestContextListener

我知道日志应该按照你的逻辑进行,只是为了调试目的我补充说。

2. ReportPage - 我们得到了例外,因为这是由Spring Batch创建的不同线程,其中请求未被log.info("ReportBuilder name: {}", reportBuilder.getName()); reportController.getReportData(); 公开。

你应该有不同的逻辑来创建和注入UselessTasklet实例到Spring Batch(May Spring Batch Parameters并使用RequestContextListener你可以返回以备将来参考)


2
投票

ReportBuilder

对于本期问题,请在上面给出url检查我的答案

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


2
投票

我通过在我的文件中添加以下代码来解决此问题。

Future<ReportBuilder>

XML配置 -

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

上面我们可以使用Java配置 -

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

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

我使用的是Spring版本5.1.4.RELEASE,无需在pom中添加以下更改。

@Configuration
@WebListener
public class MyRequestContextListener extends RequestContextListener {
}

1
投票

我的答案是指OP描述的一般问题的一个特例,但我会添加它以防万一它可以帮助某人。

当使用How to add a RequestContextListener with no-xml configuration?时,Spring在应用程序上下文中放置了一个<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.10</version> </dependency> ,这个组件碰巧假设与线程绑定的servlet相关的东西。

我的代码有一个使用自动装配的@EnableOAuth2Sso的预定异步方法。这不是在OAuth2RestTemplate中运行,但是Spring注入了RestTemplate,这产生了OP描述的错误。

解决方案是进行基于名称的注射。在Java配置中:

DispatcherServlet

在使用它的类中:

OAuth2RestTemplate

现在Spring注入了预期的,无servlet的@Bean public RestTemplate pingRestTemplate() { return new RestTemplate(); }


0
投票

您只需要在bean中定义除了prototype之外需要与默认单例范围不同的范围。例如:

@Autowired
@Qualifier("pingRestTemplate")
private RestTemplate restTemplate;
© www.soinside.com 2019 - 2024. All rights reserved.