据我所知,Servlet 3规范引入了异步处理功能。除此之外,这意味着可以并且将重用相同的线程来处理另一个并发的HTTP请求。这不是革命性的,至少对于之前与NIO合作的人来说是这样。
无论如何,这导致另一个重要的事情:没有ThreadLocal
变量作为请求数据的临时存储。因为如果同一个线程突然成为另一个HTTP请求的运营商线程,请求本地数据将被暴露给另一个请求。
所有这些都是基于阅读文章的纯粹推测,我没有时间玩任何Servlet 3实现(Tomcat 7,GlassFish 3.0.X等)。
所以,问题:
ThreadLocal
将不再是一个方便的黑客来保存请求数据?ThreadLocal
来证明上述内容? 编辑:不要误会我的意思。我完全理解危险并且ThreadLocal
是一个黑客。事实上,我总是建议不要在类似的环境中使用它。但是,不管你信不信,线程上下文的使用频率比你想象的要频繁得多。一个很好的例子是Spring的OpenSessionInViewFilter
,根据它的Javadoc:
此过滤器生成Hibernate会话 通过当前线程可用, 将被自动检测 交易经理。
这不是严格ThreadLocal
(尚未检查来源)但已经听起来令人担忧。我可以想到更多类似的场景,而且丰富的Web框架使这更有可能。
简单来说,很多人在这个黑客的基础上建造了他们的沙堡,无论是否有意识。因此,斯蒂芬的答案是可以理解的,但并不完全是我所追求的。我想确认是否有人确实尝试且能够重现失败的行为,因此这个问题可以用作被同一问题困扰的其他人的参考点
答案 0 :(得分:9)
异步处理不应该打扰你,除非你明确地要求它。
例如,如果请求的过滤器链中的servlet或任何过滤器未标记为<async-supported>true</async-supported>
,则无法使请求异步。因此,您仍然可以使用常规做法进行常规请求。
但是,如果您确实需要异步处理,则需要使用适当的实践。基本上,当异步处理请求时,其处理被分成几部分。这些部分不共享线程本地状态,但是,您仍然可以在每个部分中使用线程本地状态,但您必须在部件之间手动管理状态。
答案 1 :(得分:6)
(警告:我没有详细阅读Servlet 3规范,所以我不能肯定地说该规范说明了你的想法。我只是假设它确实......)
我是否正确地假设ThreadLocal将不再是一个方便的黑客来保存请求数据?
使用ThreadLocal
始终是一种糟糕的方法,因为当工作线程完成一个请求并从另一个请求开始时,您总是冒着信息泄漏的风险。将东西作为属性存储在ServletRequest
对象中总是更好的主意。
现在你只是有另一个理由以“正确”的方式做到这一点。
有没有人玩过任何Servlet 3实现并尝试使用ThreadLocals来证明上述内容?
这不是正确的做法。它仅告诉您在测试的特定情况下特定实现的特定行为。你无法概括。
正确的方法是假设有时会发生如果规范说它可以......并设计你的webapp来考虑它。
(不要害怕!显然,在这种情况下,默认情况下不会发生这种情况。您的webapp必须明确启用异步处理功能。如果您的代码充斥着线程本地,则建议您不要这样做。 。)
除了在HTTP Session中存储数据外,还有其他类似的易于访问的黑客,你可能会建议。
不。唯一正确的答案是在ServletRequest或ServletResponse对象中存储特定于请求的数据。即使将其存储在HTTP会话中也可能是错误的,因为对于给定的会话,可能有多个请求同时处于活动状态。
答案 2 :(得分:3)
注意:黑客跟随。请谨慎使用,或者实际上不要使用。
只要您继续了解代码执行的线程,就没有理由不能安全地使用ThreadLocal。
try {
tl.set(value);
doStuffUsingThreadLocal();
} finally {
tl.remove();
}
并不是你的调用堆栈是随机切换的。哎呀,如果你想在调用堆栈深处设置ThreadLocal值然后再使用,你也可以破解它:
public class Nasty {
static ThreadLocal<Set<ThreadLocal<?>>> cleanMe =
new ThreadLocal<Set<ThreadLocal<?>>>() {
protected Set<ThreadLocal<?>> initialValue() {
return new HashSet<ThreadLocal<?>>();
}
};
static void register(ThreadLocal<?> toClean) {
cleanMe.get().add(toClean);
}
static void cleanup() {
for(ThreadLocal<?> tl : toClean)
tl.remove();
toClean.clear();
}
}
然后在设置时注册ThreadLocals,并在某个地方的finally子句中进行清理。这是你不应该做的所有可耻的wankery。对不起,我写了但是为时已晚:/
答案 3 :(得分:1)
我仍然想知道为什么人们使用腐烂的javax.servlet API来实际实现他们的servlet。我做了什么:
我有一个基类HttpRequestHandler
,它有一个私有字段用于请求,响应和一个handle()
方法,它可以抛出Exception以及一些实用程序方法来获取/设置参数,属性等。我很少需要超过5-10%的servlet API,所以这并不像听起来那么多。
在servlet处理程序中,我创建了这个类的实例,然后忘记了servlet API。
我可以扩展此处理程序类并添加作业所需的所有字段和数据。没有庞大的参数列表,没有线程本地黑客,也不担心并发。
我有一个用于单元测试的实用程序类,它使用模拟的请求和响应实现创建HttpRequestHandler
。这样,我不需要servlet环境来测试我的代码。
这解决了我的所有问题,因为我可以在init()
方法中获取数据库会话和其他内容,或者我可以在servlet和真正的处理程序之间插入工厂来执行更复杂的操作。
答案 4 :(得分:1)
我的目标是......获得一个证据,证明它已停止在Servlet 3.0容器中工作
Here就是您要求的证明。
顺便提一下,它使用的是您在问题中提到的完全相同的OEMIV过滤器,猜猜看,它打破了异步servlet处理!
编辑:这是another证明。
答案 5 :(得分:0)
一种解决方案是不使用ThreadLocal,而是使用包含要创建全局对象的静态数组的单例。该对象将包含您设置的“threadName”字段。首先将当前线程的名称(在doGet,doPost中)设置为某个随机唯一值(如UUID),然后将其存储为包含要存储在单例中的数据的对象的一部分。然后,只要代码的某些部分需要访问数据,它就会遍历数组并使用当前正在运行的threadName检查对象并检索对象。当http请求完成时,您需要添加一些清理代码以从数组中删除对象。