28

据我所知,Servlet 3 规范引入了异步处理特性。除此之外,这意味着同一个线程可以并且将被重用于处理另一个并发的 HTTP 请求。这不是革命性的,至少对于以前与 NIO 合作过的人来说。

无论如何,这导致了另一个重要的事情:没有ThreadLocal变量作为请求数据的临时存储。因为如果同一线程突然成为不同 HTTP 请求的载体线程,请求本地数据将暴露给另一个请求。

所有这些都是我基于阅读文章的纯粹推测,我没有时间玩任何 Servlet 3 实现(Tomcat 7、GlassFish 3.0.X 等)。

所以,问题:

  • 我是否正确地假设这ThreadLocal将不再是保留请求数据的方便黑客?
  • 有没有人玩过任何 Servlet 3 实现并尝试使用ThreadLocals 来证明上述内容?
  • 除了在 HTTP Session 中存储数据之外,您还有其他类似的易于访问的 hack 建议吗?

编辑:不要误会我的意思。我完全理解危险并ThreadLocal成为黑客。事实上,我总是建议不要在类似的情况下使用它。然而,不管你信不信,线程上下文的使用频率远远超出你的想象。一个很好的例子是 Spring OpenSessionInViewFilter,根据它的 Javadoc:

此过滤器使 Hibernate Sessions 通过当前线程可用,事务管理器将自动检测到该线程。

这并不严格ThreadLocal(尚未检查来源),但听起来已经令人震惊。我可以想到更多类似的场景,而丰富的 Web 框架使这更有可能发生。

简而言之,无论有没有意识,许多人都在这个黑客之上建造了他们的沙堡。因此斯蒂芬的回答是可以理解的,但并不完全是我所追求的。我想确认是否有人真正尝试过并且能够重现失败的行为,因此这个问题可以用作其他被同样问题困住的人的参考点。

4

6 回答 6

10

除非您明确要求,否则异步处理不应打扰您。

例如,如果 servlet 或请求的过滤器链中的任何过滤器未标记为 ,则无法使请求异步<async-supported>true</async-supported>。因此,您仍然可以对常规请求使用常规做法。

当然,如果您确实需要异步处理,则需要使用适当的做法。基本上,当请求被异步处理时,它的处理被分成几部分。这些部分不共享线程本地状态,但是,您仍然可以在每个部分中使用线程本地状态,尽管您必须在各部分之间手动管理状态。

于 2011-02-21T11:30:44.333 回答
6

(警告:我没有详细阅读 Servlet 3 规范,所以我不能肯定该规范说明了你认为它做了什么。我只是假设它确实......)

我是否正确假设 ThreadLocal 将不再是保留请求数据的方便黑客?

使用ThreadLocal总是一种糟糕的方法,因为当工作线程完成一个请求并开始另一个请求时,您总是冒着信息泄漏的风险。将东西作为属性存储在ServletRequest对象中总是一个更好的主意。

现在您有了另一个理由以“正确”的方式进行操作。

有没有人玩过任何 Servlet 3 实现并尝试使用 ThreadLocals 来证明上述内容?

这不是正确的做法。它仅告诉您在特定测试环境下特定实现的特定行为。你不能一概而论。

正确的方法是假设它有时会发生,如果规范说它可以......并设计你的 webapp 来考虑它。

(不要害怕!显然,在这种情况下,默认情况下不会发生这种情况。您的 web 应用程序必须显式启用异步处理功能。如果您的代码被线程本地人感染,建议您不要这样做......)

除了在 HTTP Session 中存储数据之外,您还可以建议其他类似的易于访问的黑客攻击。

没有。唯一正确的答案是将特定于请求的数据存储在 ServletRequest 或 ServletResponse 对象中。即使将其存储在 HTTP Session 中也可能是错误的,因为对于给定的会话,可能同时存在多个活动请求。

于 2011-02-21T00:00:14.163 回答
3

NOTE: Hacks follow. Use with caution, or really just don't use.

So long as you continue to understand which thread your code is executing in, there's no reason you can't use a ThreadLocal safely.

try {
    tl.set(value);
    doStuffUsingThreadLocal();
} finally {
    tl.remove();
}

It's not as if your call stack is switched out randomly. Heck, if there are ThreadLocal values you want to set deep in the call stack and then use further out, you can hack that too:

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

Then you register your ThreadLocals as you set them, and cleanup in a finally clause somewhere. This is all shameful wankery that you shouldn't probably do. I'm sorry I wrote it but it's too late :/

于 2011-03-02T04:02:53.063 回答
1

I'm still wondering why people use the rotten javax.servlet API to actually implement their servlets. What I do:

  • I have a base class HttpRequestHandler which has private fields for request, response and a handle() method that can throw Exception plus some utility methods to get/set parameters, attributes, etc. I rarely need more than 5-10% of the servlet API, so this isn't as much work as it sounds.

  • In the servlet handler, I create an instance of this class and then forget about the servlet API.

  • I can extend this handler class and add all the fields and data that I need for the job. No huge parameter lists, no thread local hacking, no worries about concurrency.

  • I have a utility class for unit tests that creates a HttpRequestHandler with mock implementations of request and response. This way, I don't need a servlet environment to test my code.

This solves all my problems because I can get the DB session and other things in the init() method or I can insert a factory between the servlet and the real handler to do more complex things.

于 2011-03-02T09:56:48.043 回答
1

你是通灵者!(为此+1)

我的目标是……证明这已停止在 Servlet 3.0 容器中工作

是您要求的证明。

顺便说一句,它使用的是您在问题中提到的完全相同的 OEMIV 过滤器,您猜怎么着,它破坏了异步 servlet 处理!

编辑:这是另一个证明。

于 2013-05-01T20:29:06.287 回答
0

一种解决方案是不使用 ThreadLocal ,而是使用包含要使其成为全局对象的静态数组的单例。该对象将包含您设置的“threadName”字段。您首先将当前线程的名称(在 doGet、doPost 中)设置为某个随机的唯一值(如 UUID),然后将其存储为包含您希望存储在单例中的数据的对象的一部分。然后,每当您的代码的某些部分需要访问数据时,它只需遍历数组并检查具有当前正在运行的 threadName 的对象并检索该对象。当 http 请求完成时,您需要添加一些清理代码以从数组中删除对象。

于 2018-05-02T18:48:44.420 回答