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

时间:2016-05-31 08:21:40

标签: spring multithreading servlets jersey completable-future

我现在尝试了很多东西,但我似乎错过了一块拼图。这是故事:我有一个请求范围的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,当请求线程等待结果时,我有三个并行计算内容的功能。我想要做的是以一种可以在从原始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 个答案:

答案 0 :(得分:9)

对于任何未来的冒险家:

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

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

所以RequestContextHolder有一个字段,实际上setRequestAttributes支持一个标志来使用inheritableRequestAttributesHolder。此外,如果你看RequestContextListener - &gt; 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。

答案 1 :(得分:1)

就我而言,使用OrderedRequestContextFilter可解决此问题。您还必须像这样将threadContextInheritable标志设置为true:

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