15

I tried a lot of things now but i seem to miss a piece of the puzzle. Here is the story: I have a request scoped bean that reads some SessionContext from the HttpServletRequest. This attribute is set in a filter. So this is working absolutely fine while the code runs on the correct thread.

@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);
    }
}

Now I started using java 8s new feature CompletableFuture and i have three of those features computing stuff in parallel while the request thread waits for the result. What i want to do is to promote/hand over/propagate the bean or request in a way that it can be used on child threads that have been spawned from the original http thread. In particular I would like to get the SessionContext from the HttpServletRequest from inside an asynchronous supplied CompletableFuture.

what i tried is this (replaced implementation of get):

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

But this has obviously the same result as the request scoped bean. Well "getRequest" returns null instead of an exception thrown.

As a third approach I tried this original post:

ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;

org.springframework.beans.factory.config.Scope simpleThreadScope = new SimpleThreadScope();

cbf.registerScope("simpleThreadScope", simpleThreadScope);

And i set the scope of the SessionContextProviderImpl to be "simpleThreadScope". Unfortunately this did not work either and threw an exception that it is used outside of a request scope.

The environment I am using: Jersey together with spring injection.

Maybe anyone has some idea?

regards

Community
  • 1
  • 1
jonas.hartwig
  • 857
  • 3
  • 8
  • 19

2 Answers2

17

For any future adventurers:

I took some time to dig through the Spring code and found RequestContextHolder that has a inheritableRequestAttributesHolder. If you look at the documentation of what that is (inheriting from: InheritableThreadLocal) one can read the following:

Inheritable thread-local variables are used in preference to ordinary thread-local variables when the per-thread-attribute being maintained in the variable (e.g., User ID, Transaction ID) must be automatically transmitted to any child threads that are created.

So the RequestContextHolder has a field for that and actually setRequestAttributes supports a flag to use inheritableRequestAttributesHolder. Furthermore if you look at RequestContextListener -> requestInitialized you find that it is called without the flag (= false). So what I ended up doing is this:

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);
    }
}

And voila, I can access SessionContextProvider in child threads.

jonas.hartwig
  • 857
  • 3
  • 8
  • 19
  • 1
    How are you using the requestattributes after the request is completed. I am loosing my request attributes such as headers attributes once the request is completed. I tried to use the RequestContextHolder.setRequestAttributes(attributes, true); still no luck. – Shafs Jan Oct 05 '17 at 20:40
  • If you want to use the context on a thread which will not bock the request execution and thus act completely independent you should look for different solutions like this: DelegatingSecurityContextExecutor https://docs.spring.io/spring-security/site/docs/current/reference/html/concurrency.html – jonas.hartwig Oct 11 '17 at 11:17
  • 1
    Is it a component? A configuration class? What annotation do I need to put on the class? – Guy Smorodinsky Feb 19 '18 at 09:09
  • It is a context listener. As such it needs to be registered in the spring runtime environment. – jonas.hartwig Apr 04 '18 at 12:01
  • This is a great idea but unfortunately did not work for me with Spring 1.5.17, I suspect that even after setting this flag to true the request attributes are gone or not handled correctly so it probably takes more than this to get it to work. Here is a related open issue https://jira.spring.io/browse/SPR-6873 – Uncle Long Hair Nov 11 '18 at 15:25
  • Hi, did you register your handler correctly? Try debugging if it is called. This worked for me (quite some time ago, I am quite sure though there was no other magic) – jonas.hartwig Jan 03 '19 at 08:24
3

In my case using OrderedRequestContextFilter resolve the issue. You have to also set threadContextInheritable flag to true like this :

@Bean
public RequestContextFilter requestContextFilter() {
    OrderedRequestContextFilter filter = new OrderedRequestContextFilter();
    filter.setThreadContextInheritable(true);
    return filter;
}
Marcin
  • 31
  • 3
  • 1
    Be aware of the warning right there in the documentation: `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.` – rougou Jul 30 '19 at 01:27