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

Jon*_*Jon 54 spring wicket

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

1.初始Web配置

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

web.xml在文档中显示了以下内容:

<listener>
  <listener-class>
    org.springframework.web.context.request.RequestContextListener
  </listener-class>
</listener>
Run Code Online (Sandbox Code Playgroud)

2. Scoped bean作为依赖项

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

@Scope提供了proxyMode如下所示的bean注释:

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

问题

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

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>
Run Code Online (Sandbox Code Playgroud)

以下内容AppConfig.java:

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

更新2

我创建了一个可重现的测试用例.这是一个小得多的项目,因此存在差异,但会发生同样的错误.有相当多的文件,所以我上传它作为一个tar.gzmegafileupload.

Mar*_*ejc 52

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

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

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

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


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

  • `RequestContextListener`的目的不是让请求可用于其他线程吗?报告构建器按步骤构建报告.在构建报告时,页面通过AJAX更新. (2认同)

Jon*_*Jon 33

我正在回答我自己的问题,因为它可以更好地概述原因和可能的解决方案.我已将奖金授予@Martin,因为他指出了原因.

原因

正如@Martin所建议的那样,原因是使用了多个线程.请求对象在这些线程中不可用,如Spring Guide中所述:

DispatcherServlet,RequestContextListener并且RequestContextFilter所有操作完全相同,即将HTTP请求对象绑定到为该请求提供服务的Thread.这使得请求和会话范围的bean可以在调用链中进一步使用.

解决方案1

可以将请求对象提供给其他线程,但是它对系统有一些限制,这可能在所有项目中都不可行.我从多线程Web应用程序中访问请求范围bean获得了此解决方案:

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

解决方案2

唯一的另一种选择是在请求线程中使用会话范围的bean.在我的情况下,这是不可能的,因为:

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

  • 我尝试了所有这些...都不起作用。我不知道为什么到处都在建议这些解决方案,但它们不起作用。对于仍然遇到此问题的人,请查看此链接:https://medium.com/@pranav_maniar/spring-accessing-request-scope-beans-outside-of-web-request-faad27b5ed57 (2认同)

Moh*_*hit 32

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

在web.xml中

 <listener>
            <listener-class>
                    org.springframework.web.context.request.RequestContextListener 
            </listener-class>
  </listener>
Run Code Online (Sandbox Code Playgroud)

在Session组件中

@Component
@Scope(value = "session",  proxyMode = ScopedProxyMode.TARGET_CLASS)
Run Code Online (Sandbox Code Playgroud)

在pom.xml中

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.1</version>
    </dependency>
Run Code Online (Sandbox Code Playgroud)


Man*_*ani 7

由于每个文件:

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

如果你在Spring MVC之外运行(不由DispatchServlet处理),你必须使用RequestContextListenerNot just ContextLoaderListener.

在web.xml中添加以下内容

   <listener>
            <listener-class>
                    org.springframework.web.context.request.RequestContextListener 
            </listener-class>
    </listener>        
Run Code Online (Sandbox Code Playgroud)

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

更新:根据其他答案,@Controller当你在Spring MVC Context中使用时,唯一合理的,所以@Controller不能在你的代码中实现目的.您仍然可以将bean注入任何具有会话范围/请求范围的位置(您不需要Spring MVC/Controller来在特定范围内注入bean).

更新: RequestContextListener仅请求公开给当前线程.
你已经在两个地方自动装配了ReportBuilder

1. ReportPage- 你可以看到Spring在这里正确地注入了Report构建器,因为我们仍然在Same web Thread中.我确实改变了你的代码的顺序,以确保ReportBuilder像这样注入ReportPage.

log.info("ReportBuilder name: {}", reportBuilder.getName());
reportController.getReportData();
Run Code Online (Sandbox Code Playgroud)

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


2. UselessTasklet- 我们得到了异常,这是因为这是由Spring Batch创建的不同线程,其中请求未公开RequestContextListener.


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


Nom*_*tar 7

我通过在我的文件中添加以下代码解决了这个问题。

@Component
@Scope(value = "session",  proxyMode = ScopedProxyMode.TARGET_CLASS)
Run Code Online (Sandbox Code Playgroud)

XML 配置 -

<listener>
        <listener-class>
            org.springframework.web.context.request.RequestContextListener 
        </listener-class>
</listener>
Run Code Online (Sandbox Code Playgroud)

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

@Configuration
@WebListener
public class MyRequestContextListener extends RequestContextListener {
}
Run Code Online (Sandbox Code Playgroud)

如何添加无 xml 配置的 RequestContextListener?

我正在使用 spring 版本 5.1.4.RELEASE 并且不需要在pom.xml中添加以下更改。

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.10</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)