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

Posted

技术标签:

【中文标题】Spring将请求范围的bean提升到子线程(HttpServletRequest)【英文标题】:Spring promoting request scoped bean to child threads (HttpServletRequest) 【发布时间】:2016-09-29 03:46:24 【问题描述】:

我现在尝试了很多东西,但我似乎错过了一块拼图。这是故事:我有一个请求范围的 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);
    

现在我开始使用 java 8s 的新功能 CompletableFuture,并且在请求线程等待结果时,我拥有其中三个并行计算内容的功能。我想要做的是提升/移交/传播bean或请求,使其可用于从原始http线程产生的子线程。特别是我想从异步提供的 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”。不幸的是,这也不起作用,并引发了一个异常,即它在请求范围之外使用。

我使用的环境:Jersey 和 spring 注入。

也许有人有什么想法?

问候

【问题讨论】:

【参考方案1】:

在我的情况下,使用 OrderedRequestContextFilter 解决问题。您还必须像这样将 threadContextInheritable 标志设置为 true :

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

【讨论】:

请注意文档中的警告:WARNING: Do not use inheritance for child threads if you are accessing a thread pool which is configured to potentially add new threads on demand (e.g. a JDK ThreadPoolExecutor), since this will expose the inherited context to such a pooled thread.【参考方案2】:

对于任何未来的冒险家:

我花了一些时间挖掘 Spring 代码,发现 RequestContextHolder 有一个可继承的RequestAttributesHolder。如果您查看文档(继承自:InheritableThreadLocal),您可以阅读以下内容:

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

所以RequestContextHolder 有一个字段,实际上setRequestAttributes 支持使用inheritableRequestAttributesHolder 的标志。此外,如果您查看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。

【讨论】:

请求完成后如何使用请求属性。请求完成后,我将丢失请求属性,例如标头属性。我尝试使用 RequestContextHolder.setRequestAttributes(attributes, true);仍然没有运气。 如果您想在不会阻塞请求执行并因此完全独立运行的线程上使用上下文,您应该寻找不同的解决方案,例如:DelegatingSecurityContextExecutor docs.spring.io/spring-security/site/docs/current/reference/html/… 它是一个组件吗?配置类?我需要在课堂上添加什么注释? 它是一个上下文监听器。因此它需要在 spring 运行时环境中注册。 这是一个好主意,但不幸的是在 Spring 1.5.17 中对我不起作用,我怀疑即使在将此标志设置为 true 后,请求属性也已消失或未正确处理,因此可能需要更多时间比这让它工作。这是一个相关的未解决问题jira.spring.io/browse/SPR-6873

以上是关于Spring将请求范围的bean提升到子线程(HttpServletRequest)的主要内容,如果未能解决你的问题,请参考以下文章

是否可以使用会话中的属性配置 Spring 会话范围的 bean?

Spring Bean单例与线程安全

xml 如果使用JSF作为MVC框架,则使请求和会话范围spring bean可用

Spring会话范围的bean作为原型bean中的依赖项?

JSF 2.x @ViewScoped 托管 bean 线程安全吗?

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