java - ScopedProxy 如何决定使用什么 Session?

标签 java spring singleton session-scope

Singleton 不能 Autowiring SessionBean,但 ScopedProxy 可以。

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

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

  1. ScopedProxy 如何决定使用哪个 session ?
  2. 如果 0 个用户有一个 session 怎么办?会发生 NullPointerException 吗?
  3. @Async 与调用 Request-Processing-Thread 是不同的线程,如何将 HttpRequest-Context 注入(inject)到 Async 任务中?

最佳答案

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

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 有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您将看到以下内容:

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 实现(返回此线程局部变量的当前线程副本中的值):

/**
 * 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

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

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

( Spring Framework Reference Documentation ) 表示:

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.

您还可以检查以下问题:Accessing request scoped beans in a multi-threaded web application

@Async 和请求属性注入(inject)

一般来说,没有直接的方法可以解决问题。如前所述,我们有线程绑定(bind)的 RequestAttributes。

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

更聪明的解决方案(由 Eugene Kuleshov 建议)是透明地执行此操作。为了方便阅读,我把代码复制下来,把链接放在代码块下面。

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

这是那个问题:Accessing scoped proxy beans within Threads of

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

这是另一个非常有趣的话题 @Async annotated method hanging on session-scoped bean

关于java - ScopedProxy 如何决定使用什么 Session?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33038856/

相关文章:

spring - 如何在 Spring Boot Health 中添加自定义健康检查?

spring - 如何避免两次进行 spring 初始化?

java - 使用参数装饰 Resilience4j 断路器中的功能

design-patterns - ExtJS 有标准的设计模式吗

java - 单例 servlet?

java - 使用单向或双向关系的不同行为

java - 使用 lambda java 查找最大值

java - 空对象 - 实例化或单例的东西?

java - 通过jsp将变量从一个jsp传递到另一个jsp页面时出错:include tag

java - 如何将表导入本地文件系统?