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

ⅰ亾dé卋堺 提交于 2019-11-28 06:20:13

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.

The configuration below will propagate request context to your threads launched from within HTTP request:

<servlet>
    <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>threadContextInheritable</param-name>
      <param-value>true</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

Disclaimer: I havent tested this specifically with request-scoped beans as I dont use any. I did test that RequestContextHolder returns valid context in child threads.

Disclaimer 2: there is a reason this setting defaults to false. There may be side-effects, especially if you reuse your threads (as in threadpools).

If you have a look at AbstractRequestAttributesScope you'll see that it's using the current RequestAttributes in order to get the desired bean.

In your thread you'll probably want to do something like this:

final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
final SecurityContext securityContext = SecurityContextHolder.getContext();

new Thread(
    () -> {

      boolean hasContext = RequestContextHolder.getRequestAttributes() == requestAttributes
          && SecurityContextHolder.getContext() == securityContext;

      if (!hasContext) {
        RequestContextHolder.setRequestAttributes(requestAttributes);
        SecurityContextHolder.setContext(securityContext);
      }

      try {

        // useful stuff goes here

      } finally {
        if (!hasContext) {
          RequestContextHolder.resetRequestAttributes();
          SecurityContextHolder.clearContext();
        }
      }
    }
).start();  

Inspired by @mael's answer, here is my "custom-scope-out-of-the-box" solution. I am using a fully annotation-driven Spring configuration.

For my particular case, Spring's own org.springframework.context.support.SimpleThreadScope already provides the behavior the question is looking for (right, that's weird, because SimpleThreadScope doesn't use an InheritableThreadLocal, but effectively a ThreadLocal. But as it works, I'm already happy).

Correct behavior at concurrent user interaction has not been tested yet.

Steps

Register the SimpleThreadScope type:

package com.example.config

public class MainConfig implements BeanFactoryAware {

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

    .......

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        if (beanFactory instanceof ConfigurableBeanFactory) {

            logger.info("MainConfig is backed by a ConfigurableBeanFactory");
            ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;

            /*Notice:
             *org.springframework.beans.factory.config.Scope
             * !=
             *org.springframework.context.annotation.Scope
             */
            org.springframework.beans.factory.config.Scope simpleThreadScope = new SimpleThreadScope();
            cbf.registerScope("simpleThreadScope", simpleThreadScope);

            /*why the following? Because "Spring Social" gets the HTTP request's username from
             *SecurityContextHolder.getContext().getAuthentication() ... and this 
             *by default only has a ThreadLocal strategy...
             *also see http://stackoverflow.com/a/3468965/923560 
             */
            SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);

        }
        else {
            logger.info("MainConfig is not backed by a ConfigurableBeanFactory");
        } 
    }
}

Now for any bean that shall have request-scope and that shall be usable from any thread spawned by the HTTP request thread, set the newly defined scope accordingly:

package com.example.config

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

    private Integer counter = new Integer(0);

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


    @Bean
    @Scope(value = "simpleThreadScope", proxyMode = ScopedProxyMode.INTERFACES)
    public ConnectionRepository connectionRepository() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null) {
            throw new IllegalStateException("Unable to get a ConnectionRepository: no user signed in");
        }
        return usersConnectionRepository().createConnectionRepository(authentication.getName());
    }


    @Bean
    @Scope(value = "simpleThreadScope", proxyMode = ScopedProxyMode.INTERFACES)
    public Facebook facebook() {
    Connection<Facebook> facebook = connectionRepository().findPrimaryConnection(Facebook.class);
    return facebook != null ? facebook.getApi() : new FacebookTemplate();
    }


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

}
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. If you use a Servlet 2.5 web container, with requests processed outside of Spring’s DispatcherServlet (for example, when using JSF or Struts), you need to register the org.springframework.web.context.request.RequestContextListener ServletRequestListener. For Servlet 3.0+, this can done programmatically via the WebApplicationInitializer interface. Alternatively, or for older containers, add the following declaration to your web application’s web.xml file:

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!