代码之家  ›  专栏  ›  技术社区  ›  jonas.hartwig

Spring将请求范围的bean提升到子线程(HttpServletRequest)

  •  15
  • jonas.hartwig  · 技术社区  · 8 年前

    我现在试了很多东西,但似乎错过了拼图的一部分。故事是这样的:我有一个请求范围的bean,它从HttpServletRequest读取一些SessionContext。此属性在过滤器中设置。因此,当代码在正确的线程上运行时,这是绝对正常的。

    @Component
    @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.INTERFACES)
    public class SessionContextProviderImpl implements SessionContextProvider<SessionContext> {
        private final HttpServletRequest _request;
    
        @Autowired
        public SessionContextProviderImpl(HttpServletRequest request) {
            _request = request;
        }
    
        @Override
        public SessionContext get() {
            return (SessionContext) _request.getAttribute(Constants.SESSION_CONTEXT_IDENTIFIER);
        }
    }
    

    现在我开始使用java8的新特性CompletableFuture,我有三个特性并行计算,而请求线程等待结果。我想做的是以一种可以在从原始http线程派生的子线程上使用的方式来提升/移交/传播bean或请求。特别是,我想从异步提供的CompletableFuture内部的HttpServletRequest获取SessionContext。

    我尝试的是这样(替换了get的实现):

    final HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    request.getAttribute(Constants.SESSION_CONTEXT_IDENTIFIER);
    

    但这显然与请求范围的bean具有相同的结果。“getRequest”返回null,而不是抛出异常。

    作为第三种方法,我尝试了 original post :

    ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
    
    org.springframework.beans.factory.config.Scope simpleThreadScope = new SimpleThreadScope();
    
    cbf.registerScope("simpleThreadScope", simpleThreadScope);
    

    我将SessionContextProviderImpl的范围设置为“simpleThreadScope”。不幸的是,这也不起作用,并引发了一个异常,该异常在请求范围之外使用。

    我使用的环境:泽西岛加弹簧注射。

    也许有人有主意?

    当做

    2 回复  |  直到 7 年前
        1
  •  17
  •   jonas.hartwig    8 年前

    对于未来的冒险家:

    我花了一些时间仔细研究Spring代码,发现 RequestContextHolder 具有可继承RequestAttributesHolder的。如果您查看这是什么的文档(继承自: InheritableThreadLocal )可以阅读以下内容:

    当变量中维护的每线程属性(如用户ID、事务ID)必须自动传输到创建的任何子线程时,可继承线程局部变量优先于普通线程局部变量使用。

    所以 请求上下文保持器 有一个字段,实际上setRequestAttributes支持使用inheritableRequestAttibutesHolder的标志。此外,如果你看 RequestContextListener ->requestInitialized会发现调用它时没有标记(=false)。所以我最终做的是:

    public class InheritableRequestContextListener extends RequestContextListener {
        private static final String REQUEST_ATTRIBUTES_ATTRIBUTE =
            InheritableRequestContextListener.class.getName() + ".REQUEST_ATTRIBUTES";
    
        @Override
        public void requestInitialized(ServletRequestEvent requestEvent) {
            if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) {
                throw new IllegalArgumentException(
                        "Request is not an HttpServletRequest: " + requestEvent.getServletRequest());
            }
            HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();
            ServletRequestAttributes attributes = new ServletRequestAttributes(request);
            request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);
            LocaleContextHolder.setLocale(request.getLocale());
            RequestContextHolder.setRequestAttributes(attributes, true);
        }
    }
    

    瞧,我可以在子线程中访问SessionContextProvider。

        2
  •  3
  •   Marcin    6 年前

    在我的案例中,使用 OrderedRequestContextFilter 解决问题。您还必须将threadContextInheritable标志设置为true,如下所示:

    @Bean
    public RequestContextFilter requestContextFilter() {
        OrderedRequestContextFilter filter = new OrderedRequestContextFilter();
        filter.setThreadContextInheritable(true);
        return filter;
    }