Singleton无法自动装配SessionBean,但ScopedProxy可以。
假设100个用户在同一个应用程序中同时拥有一个有效的Session,ScopedProxy如何决定会话的含义?
我认为ScopedProxy不会选择任何随机会话,这在我看来是无稽之谈。
答案 0 :(得分:9)
ThreadLocal几乎就是您正在寻找的答案。
此类提供线程局部变量。这些变量不同 来自他们的正常对应者,每个访问一个的线程 (通过其get或set方法)有自己的,独立初始化 变量的副本。
Spring有RequestContextHolder
Holder类以线程绑定的形式公开Web请求 RequestAttributes对象。该请求将由任何孩子继承 如果设置了可继承标志,则由当前线程生成的线程 为真。
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
实现(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()
在地图中执行查找。我希望你现在看到整个画面。
关于潜在的NullPointerException
基本上,只有在作用域处于活动状态时才能调用代理的方法,这意味着执行线程应该是一个servlet请求。因此,任何异步作业,命令等都将失败。
我想说,这是ScopedProxy
背后的一个很大的问题。它确实透明地解决了一些问题(简化了调用链,例如),但如果你不遵守规则,你可能会得到java.lang.IllegalStateException: No thread-bound request found
(Spring Framework Reference Documentation)说:
DispatcherServlet,RequestContextListener和RequestContextFilter all 做同样的事情,即将HTTP请求对象绑定到 为该请求提供服务的线程。这使豆成为 请求和会话范围可在调用链中进一步提供。
您还可以查看以下问题:Accessing request scoped beans in a multi-threaded web application
@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();
}
以下是这个问题:Accessing scoped proxy beans within Threads of
正如您所看到的,此解决方案依赖于事实构造函数将在适当的上下文中执行,因此可以缓存正确的上下文并在以后注入。
这是另一个非常有趣的主题@Async annotated method hanging on session-scoped bean
答案 1 :(得分:2)
Singleton无法自动装配SessionBean,但ScopedProxy可以。
这句话有点令人困惑。它应该改写为
Spring不能将会话范围的bean注入到单例范围的bean中 除非前者被定义为(作用域)代理。
换句话说,Spring将无法将非代理会话范围的bean注入到单例范围的bean中。它将成功地在单例范围的bean中注入一个代理的会话范围的bean。
假设100个用户同时拥有有效的会话 应用程序,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类以线程绑定的形式公开Web请求
RequestAttributes
对象。
它可以这样做,因为Servlet容器通常(即非异步)处理单个线程中的请求。如果您在该请求处理程序线程中执行代码,则可以访问该请求。如果您有权访问该请求,请you have access to the HttpSession
。
实际执行时间相当长。如果你想进入它,请从SessionScope
开始,然后逐步解决。
所有这一切都说Spring没有注入具体bean类型的对象,它注入了一个代理。以下示例使用JDK proxies(仅接口),是会话范围代理的行为。给定
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规范允许您将Async Processing和Spring MVC supports it与DefferedResult
和Callable
一起使用。有一个关于它的博客系列,here。您仍然无法传递会话范围的bean。但是,如果您引用AsyncContext
,则可以检索HttpServletRequest
并自行访问HttpSession
。
如果您正在控制调度线程的方式(或者更确切地说是Runnable
),则可以使用一些技术来复制请求上下文like the one described here。
以下是关于(会话)范围和代理的一些相关帖子:
答案 2 :(得分:2)
我会做一个非常简单的解释
@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