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?
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
As singleton beans are injected only once per their lifetime you need to provide scoped beans as proxies which takes care of that. @RequestScope is a meta-annotation on @Scope that 1) sets the scope to "request" and 2) sets the proxyMode to ScopedProxyMode.
If you want to make sure your bean is thread-safe, you should go for the @RequestScope. As the name suggests, Spring binds such bean to a particular web request. Request beans aren't shared between multiple threads, hence you don't have to care about concurrency. But hang on a minute.
A request-scoped bean is an object managed by Spring, for which the framework creates a new instance for every HTTP request. The app can use the instance only for the request that created it. Any new HTTP request (from the same or other clients) creates and uses a different instance of the same class (figure 2).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With