问题 ScopedProxy如何决定使用哪个Session?


Singleton不能自动装配SessionBean,但ScopedProxy可以。

假设100个用户在同一个应用程序中同时拥有一个有效的Session,那么ScopedProxy如何决定会话的含义?

我不认为ScopedProxy正在选择任何随机会话,这在我看来是无稽之谈。

  1. ScopedProxy如何决定使用哪个会话?
  2. 如果0个用户有会话怎么办?请问 NullPointerException 发生?
  3. @Async是一个不同的线程而不是调用Request-Processing-Thread如何将HttpRequest-Context注入Async任务?

7393
2017-10-09 12:55


起源



答案:


ThreadLocal的 几乎是你正在寻找的答案。

该类提供线程局部变量。这些变量不同   来自他们正常的对应物,每个访问一个的线程   (通过其get或set方法)有自己的,独立初始化   变量的副本。

春天了 RequestContextHolder

Holder类以线程绑定的形式公开Web请求   RequestAttributes对象。该请求将由任何孩子继承   如果设置了可继承标志,则由当前线程生成的线程   为真。

在课堂上 你会看到以下内容:

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

这是实际的setter(注意它是静态的):

/**
     * 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) {}

所以,正如你所看到的,那里没有魔法,只有一个特定于线程的变量,由。提供 ThreadLocal

如果你有足够的好奇心 ThreadLocal.get 实现(whic返回当前线程的此线程局部变量的副本中的值):

/**
 * 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();
}

正如你所看到的,它只是依赖于它 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() 在Map中执行查找。我希望你现在看到整个画面。

关于潜在的NullPointerException

基本上,只有在作用域处于活动状态时才能调用代理的方法,这意味着执行线程应该是一个servlet请求。因此,任何异步作业,命令等都将失败。

我想说,这背后是一个很大的问题 ScopedProxy。它确实透明地解决了一些问题(简化了调用链,对于examaple),但如果你不遵守规则,你可能会得到 java.lang.IllegalStateException: No thread-bound request found

Spring框架参考文档)说以下内容:

DispatcherServlet,RequestContextListener和RequestContextFilter all   做同样的事情,即将HTTP请求对象绑定到   为该请求提供服务的线程。这使豆成为   请求和会话范围可在调用链中进一步提供。

您还可以查看以下问题: 在多线程Web应用程序中访问请求范围的bean

@Async和请求属性注入

一般来说,没有直接的方法来解决问题。如前所示,我们有线程绑定的RequestAttributes。

可能的解决方案是手动传递所需的对象并确保后面的逻辑 @Async 考虑到这一点。

一个更聪明的解决方案(建议 尤金库勒霍夫)是透明地做到这一点。我将复制代码以简化阅读并将链接放在代码块下。

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

这是一个问题: 在Threads中访问作用域代理bean

正如您所看到的,此解决方案依赖于事实构造函数将在适当的上下文中执行,因此可以缓存适当的上下文并在以后注入它。

这是另一个非常有趣的主题 @Async带注释的方法挂在会话范围的bean上


9
2017-10-11 22:26



为ThreadLocal示例+1。我总是尝试使用threadlocal和http会话示例来描述范围。 - HRgiger
@PeterRader,见更新。 - Renat Gilmanov


答案:


ThreadLocal的 几乎是你正在寻找的答案。

该类提供线程局部变量。这些变量不同   来自他们正常的对应物,每个访问一个的线程   (通过其get或set方法)有自己的,独立初始化   变量的副本。

春天了 RequestContextHolder

Holder类以线程绑定的形式公开Web请求   RequestAttributes对象。该请求将由任何孩子继承   如果设置了可继承标志,则由当前线程生成的线程   为真。

在课堂上 你会看到以下内容:

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

这是实际的setter(注意它是静态的):

/**
     * 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) {}

所以,正如你所看到的,那里没有魔法,只有一个特定于线程的变量,由。提供 ThreadLocal

如果你有足够的好奇心 ThreadLocal.get 实现(whic返回当前线程的此线程局部变量的副本中的值):

/**
 * 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();
}

正如你所看到的,它只是依赖于它 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() 在Map中执行查找。我希望你现在看到整个画面。

关于潜在的NullPointerException

基本上,只有在作用域处于活动状态时才能调用代理的方法,这意味着执行线程应该是一个servlet请求。因此,任何异步作业,命令等都将失败。

我想说,这背后是一个很大的问题 ScopedProxy。它确实透明地解决了一些问题(简化了调用链,对于examaple),但如果你不遵守规则,你可能会得到 java.lang.IllegalStateException: No thread-bound request found

Spring框架参考文档)说以下内容:

DispatcherServlet,RequestContextListener和RequestContextFilter all   做同样的事情,即将HTTP请求对象绑定到   为该请求提供服务的线程。这使豆成为   请求和会话范围可在调用链中进一步提供。

您还可以查看以下问题: 在多线程Web应用程序中访问请求范围的bean

@Async和请求属性注入

一般来说,没有直接的方法来解决问题。如前所示,我们有线程绑定的RequestAttributes。

可能的解决方案是手动传递所需的对象并确保后面的逻辑 @Async 考虑到这一点。

一个更聪明的解决方案(建议 尤金库勒霍夫)是透明地做到这一点。我将复制代码以简化阅读并将链接放在代码块下。

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

这是一个问题: 在Threads中访问作用域代理bean

正如您所看到的,此解决方案依赖于事实构造函数将在适当的上下文中执行,因此可以缓存适当的上下文并在以后注入它。

这是另一个非常有趣的主题 @Async带注释的方法挂在会话范围的bean上


9
2017-10-11 22:26



为ThreadLocal示例+1。我总是尝试使用threadlocal和http会话示例来描述范围。 - HRgiger
@PeterRader,见更新。 - Renat Gilmanov


我会做一个非常简单的解释

@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  

2
2017-10-14 08:52





Singleton不能自动装配SessionBean,但ScopedProxy可以。

这种说法有点令人困惑。它应该改写为

Spring不能将会话范围的bean注入到单例范围的bean中   除非前者被定义为(作用域)代理。

换句话说,Spring将无法将非代理会话范围的bean注入到单例范围的bean中。它将成功地在单例范围的bean中注入代理的会话范围的bean。

假设100个用户同时拥有一个有效的Session   应用程序,ScopedProxy如何决定会话的含义?

要澄清的第一件事是a 会议 是Servlet容器的一个组件,由一个表示 HttpSession。 Spring(和Spring MVC)使用会话范围的bean(以及其他诸如flash属性)来抽象它。

HttpSession 对象通常通过适当的cookie与用户相关联。 HTTP请求提供带有用户标识值的cookie,Servlet容器检索或创建关联的 HttpSession。换句话说,会话可以从请求中的信息中识别。您或Spring需要访问该请求。

Spring MVC显然可以通过访问请求 DispatcherServlet,即使它通常不会将它暴露给处理程序方法(请记住Spring MVC试图隐藏Servlet API)。

以下是或多或少的实现细节。 Spring MVC,而不是传播请求对象(HttpServletRequest)一直到callstack,将它存储在一个 RequestContextHolder

Holder类以线程绑定的形式公开Web请求    RequestAttributes 目的。

它可以这样做,因为Servlet容器通常(即非异步)处理单个线程中的请求。如果您在该请求处理程序线程中执行代码,则可以访问该请求。如果您有权访问该请求, 你有权访问 HttpSession

实际实施相当长。如果你想进入它,请从开始 SessionScope 并努力工作。

所有这一切都说Spring没有注入具体bean类型的对象,它注入了一个代理。以下示例,使用 JDK代理 (只有接口),是会话作用域代理的行为。特定

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

Spring会创建一个代理 SessionScopedBean 同样(但更复杂)

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

并注入这个 proxy 对象进入你的单例bean(可能是控制器)。当你的单例bean即将使用会话范围的bean时,它实际上是通过代理,代理正在检索并委托对实际对象的调用。

如果0个用户有会话怎么办?请问 NullPointerException 发生?

Spring注入代理。代理不是 null。这是一个对象。它知道如何检索和使用实际目标。对于会话范围,如果目标不存在,则会创建并保存在会话中(然后使用)。


这里的危险是尝试在会话的上下文之外使用会话范围的代理。如前所述,这整个技巧都有效,因为Servlet容器通过在单个线程中处理单个请求来工作。如果您尝试在未绑定请求的线程中访问会话范围的bean,您将获得异常。

因此,不要尝试跨线程边界传递会话范围的bean。 Servlet规范允许您使用 异步处理 和 Spring MVC支持它 同 DefferedResult 和 Callable。有关于它的博客系列, 这里。您仍然无法传递会话范围的bean。但是,如果您有参考 AsyncContext,你可以检索 HttpServletRequest 并访问 HttpSession 你自己。

如果您正在控制调度线程的方式(或者更确切地说是 Runnables),您可以使用一些技术来复制请求上下文, 就像这里描述的那样


以下是一些关于(会话)范围和代理的相关帖子:


2
2017-10-14 01:22