在实际 Web 请求之外使用请求范围的 bean

Posted

技术标签:

【中文标题】在实际 Web 请求之外使用请求范围的 bean【英文标题】:Using a request scoped bean outside of an actual web request 【发布时间】:2013-06-29 18:29:37 【问题描述】:

我有一个 Web 应用程序,它在一个单独的线程中运行一个 Spring Integration 逻辑。问题是在某些时候我的 Spring Integration 逻辑尝试使用请求范围的 bean,然后我得到以下错误:

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.tenantContext': 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.


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.

我设置了 ContextLoaderListener:

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

我的 Scoped Bean 是这样注释的(因为我听说代理我的 bean 会有所帮助):

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)  
public class TenantContext  implements Serializable 

我正在做的事情可能吗? 如果是,我在这里错过了什么? 如果没有,关于我如何实现这一目标的任何其他建议?

【问题讨论】:

您是在请求服务后尝试运行代码,还是让请求等待一些异步处理? @OrangeDog 问题已得到回答,并且已被接受。早在 2013 年,我对很多事情一无所知,现在我明白这是一个菜鸟的错误,但还是谢谢。 我应该在那里阅读您的 cmets 以获得答案。你不希望我回答你如何实际做到这一点? 【参考方案1】:

您可以像这样在新线程中发布请求:

import org.springframework.web.context.request.RequestContextListener;
 ...
ServletRequestEvent requestEvent = new ServletRequestEvent(req.getServletContext(), req);
RequestContextListener requestContextListener = new RequestContextListener();
requestContextListener.requestInitialized(requestEvent);
 ...
requestContextListener.requestDestroyed(requestEvent);

如果您查看requestInitialized()-method 内部,您会发现一个保存请求的ThreadLocal-variable。现在您可以成功地自动装配您的请求了。

【讨论】:

【参考方案2】:

对于 spring-boot 2.4 和 spring framework 5,RequestContextFilterRequestContextListener 都不适用于我。

深入代码后发现DispatcherServlet会覆盖RequestContextHolderRequestContextFilter或其他任何一个设置的inheritable,见DispatcherServlet.processRequestDispatcherServlet.initContextHolders

所以解决方案很简单,不需要任何其他组件:

@Configuration
class whateverNameYouLike 
   @Bean
   DispatcherServlet dispatcherServlet() 
       DispatcherServlet srvl = new DispatcherServlet();
       srvl.setThreadContextInheritable(true);
       return srvl;
   

但请注意,该解决方案仅适用于当前请求线程创建的新线程,而不适用于任何线程池。

对于线程池的情况,可以依赖一个额外的包装类:

public class InheritableRequestContextTaskWrapper 
    private Map parentMDC = MDC.getCopyOfContextMap();
    private RequestAttributes parentAttrs = RequestContextHolder.currentRequestAttributes();

    public <T, R> Function<T, R> lambda1(Function<T, R> runnable) 
        return t -> 
            Map orinMDC = MDC.getCopyOfContextMap();
            if (parentMDC == null) 
                MDC.clear();
             else 
                MDC.setContextMap(parentMDC);
            

            RequestAttributes orinAttrs = null;
            try 
                orinAttrs = RequestContextHolder.currentRequestAttributes();
             catch (IllegalStateException e) 
            
            RequestContextHolder.setRequestAttributes(parentAttrs, true);
            try 
                return runnable.apply(t);
             finally 
                if (orinMDC == null) 
                    MDC.clear();
                 else 
                    MDC.setContextMap(orinMDC);
                
                if (orinAttrs == null) 
                    RequestContextHolder.resetRequestAttributes();
                 else 
                    RequestContextHolder.setRequestAttributes(orinAttrs, true);
                
            
        ;
    

然后这样使用:

InheritableRequestContextTaskWrapper wrapper = new InheritableRequestContextTaskWrapper();
List<String> res = pool.submit(() -> ids.parallelStream().map(
    wrapper.lambda1((String id) -> 
        try 
           // do something and return the result string
         catch (Exception e) 
            e.printStackTrace();
            throw new RuntimeException("Error occurred in async tasks", e);
        
    )).collect(Collectors.toList())).get();

【讨论】:

【参考方案3】:

使用 RequestContextFilter 并将属性 threadContextInheritable 设置为 true。这使得子线程继承父的上下文,其中包含请求对象本身。还要确保执行程序不会重用池中的线程,因为请求对象非常特定于该请求并且不能在各种请求之间共享。 SimpleAsyncTaskExecutor 就是一个这样的执行器。

更多信息请参考Scope 'session' is not active for the current thread; IllegalStateException: No thread-bound request found。

【讨论】:

【参考方案4】:

对于 Spring 4 框架添加 servletContext.addListener(new RequestContextListener());

public class WebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer 
    @Override
    protected Class<?>[] getRootConfigClasses() 
        return new Class[]  RootConfiguration.class ;
    

    @Override
    protected Class<?>[] getServletConfigClasses() 
        return new Class[]  WebMvcConfiguration.class ;
    

    @Override
    protected String[] getServletMappings() 
        return new String[]  "/" ;
    

    @Override
    protected Filter[] getServletFilters() 
        return new Filter[]  new HiddenHttpMethodFilter() ;
    

    **@Override
    public void onStartup(ServletContext servletContext) throws ServletException 
        super.onStartup(servletContext);
        servletContext.addListener(new RequestContextListener());
    **

【讨论】:

对于 Spring 3 在你的 web.xml 添加这个 org.springframework.web.context.request.RequestContextListener 答案被引用了好几次......我没有尝试过,但我认为它没有理由适用于衍生线程,因为我完全同意here提供的论点。【参考方案5】:

您只能在运行请求的 Web 容器线程上使用请求(和会话)范围的 bean。

我认为线程正在等待来自您的 SI 流的异步回复?

如果是这样,您可以将请求范围的 bean 绑定到消息,可能在标头中,或者在负载中的某个位置。

【讨论】:

感谢您的回答。实际上我不认为该线程正在等待回复,因为我只是想保留一个对象并且我需要来自该 TenantContext bean 的信息才能这样做。 那么你根本不能使用请求范围的bean,因为根据定义,请求不再存在。 非常感谢 Gary,那我会另找办法的。 文档将RequestContextListener 描述为[A] listener that exposes the request to the current thread。这似乎与您在回答中所描述的相矛盾。你能澄清一下吗? 在这种情况下,“当前线程”是指正在处理 HTTP 请求的 servlet 容器线程,而不是您将请求交给的任意线程。

以上是关于在实际 Web 请求之外使用请求范围的 bean的主要内容,如果未能解决你的问题,请参考以下文章