How does a ScopedProxy decide what Session to use?

旧街凉风 提交于 2019-11-29 01:53:07
Renat Gilmanov

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

I will make a very simple explanation

@Component
@Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)
class YourScopedProxy {

public String dosomething() {
        return "Hello"; 
     }

}


@Component
class YourSingleton {
 @Autowired private YourScopedProxy meScopedProxy;

 public String usedosomething(){
  return this.meScopedProxy.dosomething();
 }
}


   1. How does the ScopedProxy decide what session to use?

we have 100 users

1 user (http session) call YourSingleton.usedosomething => call meScopedProxy :  
=> meScopedProxy  is not the YourScopedProxy  (original)  but a proxy to the YourScopedProxy
   and this proxy understands the scope 
=>   in this case : proxy get real 'YourScopedProxy' object from  HTTP Session  


   2. What if 0 users have a Session? Will a NullPointerException occur?
No because meScopedProxy is a proxy , when u use it 
=> proxy get real 'YourScopedProxy' object from  HTTP Session  
Sotirios Delimanolis

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

This statement is kind of confusing. It should be reworded as

Spring cannot inject session-scoped beans into singleton-scoped beans unless the former are defined as (scoped)proxies.

In other words, Spring will fail to inject a non-proxied session-scoped bean into a singleton-scoped bean. It will succeed at injecting a proxied session-scoped bean in a singleton-scoped bean.

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

The first thing to clarify is that a session is a component of a Servlet container, represented by an HttpSession. Spring (and Spring MVC) abstract it away with session-scoped beans (and other things such as flash attributes).

HttpSession objects are typically associated to a user through appropriate cookies. The HTTP request provides a cookie with a user-identifying value and the Servlet container retrieves or creates an associated HttpSession. In other words, a session is identifiable from information in the request. You or Spring needs access to the request.

Spring MVC obviously has access to the request through the DispatcherServlet, even though it doesn't typically expose it to handler methods (remember that Spring MVC tries to hide the Servlet API from you).

The following is more or less an implementation detail. Spring MVC, instead of propagating the request object (HttpServletRequest) all the way up the callstack, will store it in a RequestContextHolder.

Holder class to expose the web request in the form of a thread-bound RequestAttributes object.

It can do this because Servlet containers typically (ie. non-async) handle requests in a single thread. If you're executing code in that request handler thread, you have access to the request. And if you have access to the request, you have access to the HttpSession.

The actual implementation is rather long. If you want to go into it, start with SessionScope and work your way out.

All this to say that Spring doesn't inject an object of the concrete bean type, it injects a proxy. The following example, using JDK proxies (only interfaces), is what the behavior of session scoped proxies is like. Given

interface SessionScopedBean {...}
class SessionScopedBeanImpl implements SessionScopedBean {...}

Spring would create a proxy SessionScopedBean similarly (but much more sophisticated) to

SessionScopedBean proxy = (SessionScopedBean) Proxy.newProxyInstance(Sample.class.getClassLoader(),
        new Class<?>[] { SessionScopedBean.class }, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                HttpSession session = ...;// get session through RequestContextHolder
                SessionScopedBean actual = session.getAttribute("some.bean.identifier");
                if (actual == null) {
                    // if absent, set it
                    session.setAttribute("some.bean.identifier", actual = new SessionScopedBeanImpl());
                }
                return method.invoke(actual, args); // delegate to actual object
            }
        });

and inject this proxy object into your singleton beans (presumably the controller). When your singleton bean is about to use the session-scoped bean, it's actually going through the proxy and the proxy is retrieving and delegating the call to an actual object.

What if 0 users have a Session? Will a NullPointerException occur?

Spring injects the proxy. The proxy is not null. It's an object. It knows how to retrieve and use the actual target. With session scope, if a target doesn't exist, it is created and saved in the session (and then used).


The danger here is attempting to use session-scoped proxies outside the context of a session. As stated earlier, this whole trick works because Servlet containers work by handling a single request within a single thread. If you try to access a session-scoped bean in a thread where a request isn't bound, you'll get an exception.

As such, don't try to pass session-scoped beans across threads boundaries. The Servlet specification allows you to use Async Processing and Spring MVC supports it with DefferedResult and Callable. There's a blog series about it, here. You still can't pass around the session-scoped bean. However, if you have a reference to the AsyncContext, you can retrieve the HttpServletRequest and access the HttpSession yourself.

If you are controlling how you dispatch the threads (or rather the Runnables), there are techniques you can use to copy the request context, like the one described here.


Here are some related posts about (session) scopes and proxies:

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!