Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

spring

wicket

I have a controller that I'd like to be unique per session. According to the spring documentation there are two details to the implementation:

1. Initial web configuration

To support the scoping of beans at the request, session, and global session levels (web-scoped beans), some minor initial configuration is required before you define your beans.

I've added the following to my web.xml as shown in the documentation:

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

2. Scoped beans as dependencies

If you want to inject (for example) an HTTP request scoped bean into another bean, you must inject an AOP proxy in place of the scoped bean.

I've annotated the bean with @Scope providing the proxyMode as shown below:

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

Problem

In spite of the above configuration, I get the following exception:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.reportBuilder': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

Update 1

Below is my component scan. I have the following in 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>

And the following in AppConfig.java:

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

Update 2

I've created a reproducible test case. This is a much smaller project, so there are differences, but the same error happens. There's quite a few files, so I've uploaded it as a tar.gz to megafileupload.

like image 264
Jon Avatar asked Jan 22 '14 15:01

Jon


5 Answers

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?

like image 127
Martin Strejc Avatar answered Oct 27 '22 13:10

Martin Strejc


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.
like image 32
Jon Avatar answered Oct 27 '22 11:10

Jon


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>
like image 39
Mohit Avatar answered Oct 27 '22 12:10

Mohit


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>
like image 10
Noman Akhtar Avatar answered Oct 27 '22 12:10

Noman Akhtar


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)

like image 7
Mani Avatar answered Oct 27 '22 12:10

Mani