Spring MVC: How to use a request-scoped bean inside a spawned thread?

Abdull picture Abdull · Feb 20, 2013 · Viewed 35.4k times · Source

In a Spring MVC application, I have a request-scoped bean. I inject this bean somewhere. There, the HTTP-request serving thread could possibly spawn a new thread.

But whenever I try accessing the request-scoped bean from the newly spawned thread, I get a org.springframework.beans.factory.BeanCreationException (see stack trace below).
Accessing the request-scoped bean from the HTTP request thread works fine.

How can I make a request-scoped bean available to threads spawned by the HTTP request thread?


Simple setup

Get the following code snippets running. Then start up a server, for instance at http://example.com:8080.
When accessing http://example.com:8080/scopetestnormal, each time a request is made to this address, counter is incremented by 1 (noticeable via logger output). :) Super!

When accessing http://example.com:8080/scopetestthread, each time a request is made to this address, the mentioned exceptions are thrown. :(. No matter what chosen ScopedProxyMode, this happens for both CGLIB-based and JDK-dynamic-proxy-interface-based request-scoped beans

Configuration file

package com.example.config

@Configuration
@ComponentScan(basePackages = { "com.example.scopetest" })
public class ScopeConfig {

    private Integer counter = new Integer(0);

    @Bean
    @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
    public Number counter() {
        counter = new Integer(counter.intValue() + 1);
        return counter;
    }


    /* Adding a org.springframework.social.facebook.api.Facebook request-scoped bean as a real-world example why all this matters
    @Bean
    @Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
    public Facebook facebook() {
    Connection<Facebook> facebook = connectionRepository()
            .findPrimaryConnection(Facebook.class);
    return facebook != null ? facebook.getApi() : new FacebookTemplate();
    }
    */

    ...................

}

Controller file

package com.example.scopetest;

import javax.inject.Inject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.social.facebook.api.Facebook;
import org.springframework.social.facebook.api.FacebookProfile;
import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ScopeTestController {

    //@Inject
    //private Facebook facebook;

    @Inject
    private Number counter;

    private static final Logger logger = LoggerFactory
            .getLogger(ScopeTestController.class);

    @RequestMapping(value = "/scopetestnormal") 
    public void scopetestnormal() {
        logger.debug("About to interact with a request-scoped bean from HTTP request thread");
        logger.debug("counter is: {}", counter);

        /* 
         * The following also works
         * FacebookProfile profile = facebook.userOperations().getUserProfile();
         * logger.debug("Facebook user ID is: {}", profile.getId());    
         */
    }



    @RequestMapping(value = "/scopetestthread")
    public void scopetestthread() {
        logger.debug("About to spawn a new thread");
        new Thread(new RequestScopedBeanAccessingThread()).start();
        logger.debug("Spawned a new thread");
    }


    private class RequestScopedBeanAccessingThread implements Runnable {

        @Override
        public void run() {
            logger.debug("About to interact with a request-scoped bean from another thread. Doomed to fail.");          
            logger.debug("counter is: {}", counter);

            /*
             * The following is also doomed to fail
             * FacebookProfile profile = facebook.userOperations().getUserProfile();
             * logger.debug("Facebook user ID is: {}", profile.getId());        
             */
        }

    }

}

Stack trace for CGLIB-based request-scoped bean (proxyMode = ScopedProxyMode.TARGET_CLASS)

SLF4J: Failed toString() invocation on an object of type [$java.lang.Number$$EnhancerByCGLIB$$45ffcde7]
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.counter': Scope 'request' 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.
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:342)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
    at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:33)
    at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.getTarget(Cglib2AopProxy.java:654)
    at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:605)
    at $java.lang.Number$$EnhancerByCGLIB$$45ffcde7.toString(<generated>)
    at org.slf4j.helpers.MessageFormatter.safeObjectAppend(MessageFormatter.java:304)
    at org.slf4j.helpers.MessageFormatter.deeplyAppendParameter(MessageFormatter.java:276)
    at org.slf4j.helpers.MessageFormatter.arrayFormat(MessageFormatter.java:230)
    at ch.qos.logback.classic.spi.LoggingEvent.<init>(LoggingEvent.java:114)
    at ch.qos.logback.classic.Logger.buildLoggingEventAndAppend(Logger.java:447)18:09:48.276 container [Thread-16] DEBUG c.g.s.c.c.god.ScopeTestController - counter is: [FAILED toString()]

    at ch.qos.logback.classic.Logger.filterAndLog_1(Logger.java:421)
    at ch.qos.logback.classic.Logger.debug(Logger.java:514)
    at com.example.scopetest.ScopeTestController$RequestScopedBeanAccessingThread.run(ScopeTestController.java:58)
    at java.lang.Thread.run(Thread.java:722)
Caused by: 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.
    at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
    at org.springframework.web.context.request.AbstractRequestAttributesScope.get(AbstractRequestAttributesScope.java:40)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:328)
    ... 14 more

Stack trace for JDK-dynamic-proxy-interface-based request-scoped bean (proxyMode = ScopedProxyMode.INTERFACES)

Exception in thread "Thread-16" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.facebook': Scope 'request' 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.
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:342)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
    at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:33)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:182)
    at $Proxy28.userOperations(Unknown Source)
    at com.example.scopetest.ScopeTestController$PrintingThread.run(ScopeTestController.java:61)
    at java.lang.Thread.run(Thread.java:722)
Caused by: 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.
    at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
    at org.springframework.web.context.request.AbstractRequestAttributesScope.get(AbstractRequestAttributesScope.java:40)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:328)
    ... 6 more

Answer

ElderMael picture ElderMael · Feb 20, 2013

OK, by reading the code in SimpleThreadScope that comes with Spring I think you can create a SimpleInheritableThreadScope by using an InheritableThreadLocal instead.

Then just use a bit of xml to register your custom scope:

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
      <property name="scopes">
          <map>
              <entry key="thread-inherited">
                  <bean class="org.mael.spring.context.support.SimpleInheritableThreadScope"/>
              </entry>
          </map>
      </property>
  </bean>

This means that when you create a bean with a thread-inherited scope, you will have access to this bean with a copy per thread and that copy will be avaliable in threads spawned by your thread i.e. a request scoped bean that can be used in threads spawned in your request thread.