javaspringsingletonsession-scope

How does a ScopedProxy decide what Session to use?


A Singleton can not autowire a SessionBean but a ScopedProxy can.

Assuming 100 users have a valid Session at the same time in the same application, how does the ScopedProxy decide what session is meant?

I don't think the ScopedProxy is choosing any random session, this would be nonsense in my opinion.

  1. How does the ScopedProxy decide what session to use?
  2. What if 0 users have a Session? Will a NullPointerException occur?
  3. A @Async is a different Thread than the invoking Request-Processing-Thread how to inject the HttpRequest-Context to the Async task?

Solution

  • ThreadLocal is pretty much the answer you are looking for.

    This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable.

    Spring has RequestContextHolder

    Holder class to expose the web request in the form of a thread-bound RequestAttributes object. The request will be inherited by any child threads spawned by the current thread if the inheritable flag is set to true.

    Inside the class you'll see the following:

    private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
                new NamedThreadLocal<RequestAttributes>("Request attributes");
    

    And here is the actual setter (note it is static):

    /**
         * Bind the given RequestAttributes to the current thread.
         * @param attributes the RequestAttributes to expose,
         * or {@code null} to reset the thread-bound context
         * @param inheritable whether to expose the RequestAttributes as inheritable
         * for child threads (using an {@link InheritableThreadLocal})
         */
        public static void setRequestAttributes(RequestAttributes attributes, boolean inheritable) {}
    

    So, as you can see, no magic there, just a thread-specific variables, provided by ThreadLocal.

    If you are curios enough here is ThreadLocal.get implementation (whic returns the value in the current thread's copy of this thread-local variable):

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
    

    As you can see it simply relies on ThreadLocalMap:

    /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {
    

    getEntry() performs a lookup within the Map. I hope you see the whole picture now.

    Regarding potential NullPointerException

    Basically, you can call proxy's methods only if the scope is active, which means executing thread should be a servlet request. So any async jobs, Commands, etc will fail with this approach.

    I would say, this is quite a big problem behind ScopedProxy. It does solve some issues transparently (simplifies call chain, for examaple), but if you don't follow the rules you'll probably get java.lang.IllegalStateException: No thread-bound request found

    (Spring Framework Reference Documentation) says the following:

    DispatcherServlet, RequestContextListener and RequestContextFilter all do exactly the same thing, namely bind the HTTP request object to the Thread that is servicing that request. This makes beans that are request- and session-scoped available further down the call chain.

    You can also check the following question: Accessing request scoped beans in a multi-threaded web application

    @Async and request attributes injection

    Generally speaking, there is no straightforward way to solve the problem. As shown earlier we have thread-bound RequestAttributes.

    Potential solution is to pass required object manually and make sure the logic behind @Async takes that into account.

    A bit more clever solution (suggested by Eugene Kuleshov) is to do that transparently. I'll copy the code in order to simplify reading and put the link under the code block.

    import org.springframework.web.context.request.RequestAttributes;
    import org.springframework.web.context.request.RequestContextHolder;
    
    /**
     * @author Eugene Kuleshov
     */
    public abstract class RequestAwareRunnable implements Runnable {
      private final RequestAttributes requestAttributes;
      private Thread thread;
    
      public RequestAwareRunnable() {
        this.requestAttributes = RequestContextHolder.getRequestAttributes();
        this.thread = Thread.currentThread();
      }
    
      public void run() {
        try {
          RequestContextHolder.setRequestAttributes(requestAttributes);
          onRun();
        } finally {
          if (Thread.currentThread() != thread) {
            RequestContextHolder.resetRequestAttributes();
          }
          thread = null;
        }
      }
    
      protected abstract void onRun();
    } 
    

    Here is that question: Accessing scoped proxy beans within Threads of

    As you can see, this solution relies on the fact constructor will be executed in the proper context, so it is possible to cache proper context and inject it later.

    Here is another, pretty interesting, topic @Async annotated method hanging on session-scoped bean