ScopedProxy如何决定使用哪个Session?
Singleton不能自动assemblySessionBean,但ScopedProxy可以。
假设100个用户在同一个应用程序中同时拥有一个有效的Session,那么ScopedProxy如何决定会话的含义?
我不认为ScopedProxy正在选择任何随机会话,这在我看来是无稽之谈。
- ScopedProxy如何决定使用哪个会话?
- 如果0个用户有会话怎么办? 会发生
NullPointerException
吗? - @Async是一个不同的线程而不是调用Request-Processing-Thread如何将HttpRequest-Context注入Async任务?
ThreadLocal几乎就是您正在寻找的答案。
该类提供线程局部变量。 这些变量与它们的正常对应物的不同之处在于,访问一个变量的每个线程(通过其get或set方法)都有自己独立初始化的变量副本。
Spring有RequestContextHolder
Holder类以线程绑定的RequestAttributes对象的forms公开Web请求。 如果可inheritance标志设置为true,则当前线程生成的任何子线程将inheritance该请求。
在课堂上你会看到以下内容:
private static final ThreadLocal requestAttributesHolder = new NamedThreadLocal ("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
背后的一个很大的问题。 它确实透明地解决了一些问题(简化了调用链,例如),但如果你不遵守规则,你可能会得到java.lang.IllegalStateException: No thread-bound request found
( Spring Framework Reference Documentation )说明如下:
DispatcherServlet,RequestContextListener和RequestContextFilter都完全相同,即将HTTP请求对象绑定到为该请求提供服务的Thread。 这使得请求和会话范围的bean可以在调用链的下游进一步使用。
您还可以检查以下问题: 在multithreadingWeb应用程序中访问请求范围的bean
@Async和请求属性注入
一般来说,没有直接的方法来解决问题。 如前所示,我们有线程绑定的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(); }
这是一个问题: 在Threads中访问作用域代理bean
正如您所看到的,此解决方案依赖于事实构造函数将在适当的上下文中执行,因此可以缓存适当的上下文并在以后注入它。
这是另一个非常有趣的话题@Async注释方法,挂在会话范围的bean上
我会做一个非常简单的解释
@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
Singleton不能自动assemblySessionBean,但ScopedProxy可以。
这种说法有点令人困惑。 它应该改写为
除非将前者定义为(作用域)代理,否则Spring不能将会话范围的bean注入到单例范围的bean中。
换句话说,Spring将无法将非代理会话范围的bean注入到单例范围的bean中。 它将成功地在单例范围的bean中注入代理的会话范围的bean。
假设100个用户在同一个应用程序中同时拥有一个有效的Session,那么ScopedProxy如何决定会话的含义?
首先要澄清的是, 会话是Servlet容器的一个组件,由HttpSession
表示。 Spring(和Spring MVC)使用会话范围的bean(以及其他诸如flash属性)来抽象它。
HttpSession
对象通常通过适当的cookie与用户相关联。 HTTP请求提供带有用户标识值的cookie,Servlet容器检索或创建关联的HttpSession
。 换句话说,会话可以从请求中的信息中识别。 您或Spring需要访问该请求。
Spring MVC显然可以通过DispatcherServlet
访问请求,即使它通常不会将它暴露给处理程序方法(请记住Spring MVC试图隐藏Servlet API)。
以下是或多或少的实现细节。 Spring MVC不是在callstack上传播请求对象( HttpServletRequest
),而是将它存储在RequestContextHolder
。
Holder类以线程绑定的
RequestAttributes
对象的forms公开Web请求。
它可以这样做,因为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,您将获得exception。
因此,不要尝试跨线程边界传递会话范围的bean。 Servlet规范允许您使用异步处理 , Spring MVC支持 DefferedResult
和Callable
。 这里有关于它的博客系列。 您仍然无法传递会话范围的bean。 但是,如果您有对AsyncContext
的引用,则可以检索HttpServletRequest
并AsyncContext
访问HttpSession
。
如果您正在控制调度线程的方式(或者更确切地说是Runnable
),则可以使用一些技术来复制请求上下文, 如此处所述 。
以下是一些关于(会话)范围和代理的相关post:
- bean的会话范围如何在Spring MVC应用程序中运行?
- 将HttpServletRequest注入Controller
- Bean属性不可读或具有无效的getter方法:getter的返回类型是否与setter的参数类型匹配? 春批
- 需要内存有效的方法来存储大量的字符串(是:在Java中的HAT-Trie实现)
- IntelliJ无法识别PATH变量
- 我可以使用iText在我的Swing应用程序中呈现PDF吗?
- 获取对象字段前值hibernate JPA
- spring jndi NamingException:名称未绑定在此Context中
- 获取HTTP状态400 – 必需的MultipartFile参数’file’在spring中不存在
- 如何将Grizzly请求注入Jersey ContainerRequestFilter
- 如何在Netbeans GUI Builder中根据父JLabel的大小自动缩放图像大小?