Scope 'session' is not active for the current thread; IllegalStateException: No thread-bound request found

帅比萌擦擦* 提交于 2019-11-26 12:49:54
Martin Strejc

The problem is not in your Spring annotations but your design pattern. You mix together different scopes and threads:

  • singleton
  • session (or request)
  • thread pool of jobs

The singleton is available anywhere, it is ok. However session/request scope is not available outside a thread that is attached to a request.

Asynchronous job can run even the request or session doesn't exist anymore, so it is not possible to use a request/session dependent bean. Also there is no way to know, if your are running a job in a separate thread, which thread is the originator request (it means aop:proxy is not helpful in this case).


I think your code looks like that you want to make a contract between ReportController, ReportBuilder, UselessTask and ReportPage. Is there a way to use just a simple class (POJO) to store data from UselessTask and read it in ReportController or ReportPage and do not use ReportBuilder anymore?

If anyone else stuck on same point, following solved my problem.

In web.xml

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

In Session component

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

In pom.xml

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

I'm answering my own question because it provides a better overview of the cause and possible solutions. I've awarded the bonus to @Martin because he pin pointed the cause.

Cause

As suggested by @Martin the cause is the use of multiple threads. The request object is not available in these threads, as mentioned in the Spring Guide:

DispatcherServlet, RequestContextListener and RequestContextFilter all do exactly the same thing, namely bind the HTTP request object to the Thread that is servicing that request. This makes beans that are request- and session-scoped available further down the call chain.

Solution 1

It is possible to make the request object available to other threads, but it places a couple of limitations on the system, which may not be workable in all projects. I got this solution from Accessing request scoped beans in a multi-threaded web application:

I managed to get around this issue. I started using SimpleAsyncTaskExecutor instead of WorkManagerTaskExecutor / ThreadPoolExecutorFactoryBean. The benefit is that SimpleAsyncTaskExecutor will never re-use threads. That's only half the solution. The other half of the solution is to use a RequestContextFilter instead of RequestContextListener. RequestContextFilter (as well as DispatcherServlet) has a threadContextInheritable property which basically allows child threads to inherit the parent context.

Solution 2

The only other option is to use the session scoped bean inside the request thread. In my case this wasn't possible because:

  1. The controller method is annotated with @Async;
  2. The controller method starts a batch job which uses threads for parallel job steps.

As per documentation:

If you are accessing scoped beans within Spring Web MVC, i.e. within a request that is processed by the Spring DispatcherServlet, or DispatcherPortlet, then no special setup is necessary: DispatcherServlet and DispatcherPortlet already expose all relevant state.

If you are runnning outside of Spring MVC ( Not processed by DispatchServlet) you have to use the RequestContextListener Not just ContextLoaderListener .

Add the following in your web.xml

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

That will provide session to Spring in order to maintain the beans in that scope

Update : As per other answers , the @Controller only sensible when you are with in Spring MVC Context, So the @Controller is not serving actual purpose in your code. Still you can inject your beans into any where with session scope / request scope ( you don't need Spring MVC / Controller to just inject beans in particular scope) .

Update : RequestContextListener exposes the request to the current Thread only.
You have autowired ReportBuilder in two places

1. ReportPage - You can see Spring injected the Report builder properly here, because we are still in Same web Thread. i did changed the order of your code to make sure the ReportBuilder injected in ReportPage like this.

log.info("ReportBuilder name: {}", reportBuilder.getName());
reportController.getReportData();

i knew the log should go after as per your logic , just for debug purpose i added .


2. UselessTasklet - We got exception , here because this is different thread created by Spring Batch , where the Request is not exposed by RequestContextListener.


You should have different logic to create and inject ReportBuilder instance to Spring Batch ( May Spring Batch Parameters and using Future<ReportBuilder> you can return for future reference)

Deepak

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

For This Issue check My answer at above given url

Using a request scoped bean outside of an actual web request

My answer refers to a special case of the general problem the OP describes, but I'll add it just in case it helps somebody out.

When using @EnableOAuth2Sso, Spring puts an OAuth2RestTemplate on the application context, and this component happens to assume thread-bound servlet-related stuff.

My code has a scheduled async method that uses an autowired RestTemplate. This isn't running inside DispatcherServlet, but Spring was injecting the OAuth2RestTemplate, which produced the error the OP describes.

The solution was to do name-based injection. In the Java config:

@Bean
public RestTemplate pingRestTemplate() {
    return new RestTemplate();
}

and in the class that uses it:

@Autowired
@Qualifier("pingRestTemplate")
private RestTemplate restTemplate;

Now Spring injects the intended, servlet-free RestTemplate.

I fixed this issue by adding following code in my file.

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

XML configuration -

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

Above we can do using Java configuration -

@Configuration
@WebListener
public class MyRequestContextListener extends RequestContextListener {
}

How to add a RequestContextListener with no-xml configuration?

I am using spring version 5.1.4.RELEASE and no need to add below changes in pom.

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

You just need to define in your bean where you need a different scope than default singleton scope except prototype. For example:

<bean id="shoppingCart" 
   class="com.xxxxx.xxxx.ShoppingCartBean" scope="session">
   <aop:scoped-proxy/> 
</bean>
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!