【问题标题】:Is it a bad practice to use a ThreadLocal Object for storing web request metadata?使用 ThreadLocal 对象存储 Web 请求元数据是一种不好的做法吗?
【发布时间】:2014-03-29 04:10:10
【问题描述】:

我正在开发一个分为几个模块的 j2ee webapp。我有一些元数据,例如用户名和偏好,我想从应用程序的任何地方访问它们,并且可能还收集类似于日志记录信息但特定于请求的数据并将其存储在这些元数据中,以便我可以选择将其发送回作为给用户的调试信息。

除了在从上层表示类到下层 daos 的每个方法中传递通用上下文对象或使用 AOP 之外,唯一想到的解决方案是使用与会话 BTW 非常相似的线程本地“上下文”对象,并添加一个过滤器,用于在正在进行的请求上绑定它并在响应时解除绑定。

但是这样的事情感觉有点hacky,因为这会破坏几种模式,并且在测试和调试时可能会使事情变得复杂,所以我想问一下根据您的经验,这样进行是否可以?

【问题讨论】:

    标签: java jakarta-ee thread-local


    【解决方案1】:

    ThreadLocal 是一种弥补不良设计和/或架构的技巧。这是一种可怕的做法:

    1. 它是一个或多个全局变量的池,任何语言的全局变量都是不好的做法(与全局变量相关的一整套问题 - 在网上搜索)
    2. 如果处理不当,在任何管理其线程的 J2EE 容器中都可能导致内存泄漏。

    更糟糕的做法是在各个层中使用 ThreadLocal。 从一层传递到另一层的数据应使用传输对象(一种标准模式)传递。

    很难想出一个使用 ThreadLocal 的好理由。也许如果您需要在它们之间有第三/中间层的 2 层之间传达一些值,并且您没有办法更改该中间层。但如果是这样的话,我会寻找更好的中间层。
    在任何情况下,如果您将值存储在代码中的一个特定点并在另一个点中检索它,那么它可能是可以原谅的,否则您永远不知道任何执行方法可能对 ThreadLocal 中的值产生的影响。

    【讨论】:

      【解决方案2】:

      我个人更喜欢传递一个上下文对象,因为同一个线程用于处理的事实是实现的工件,您不应该依赖这样的工件。当你想使用其他线程时,你会碰壁。

      如果这些状态被封装在一个 Context 对象中,我认为这就足够干净了。

      【讨论】:

      • 但是如何从代码中访问 Context 对象呢?您的接口的每个方法是否都将 Context 对象作为其参数之一?至于使用多个线程,每个线程都可以有自己的 Context 对象,我可以从父级中列出它们。
      • 如果您能扩展您的答案以解决@Aldian 提出的问题,那就太好了
      • @Aldian:我个人认为,如果您传递上下文对象,这很好。如果它真的失控了,您可以创建一个以上下文作为字段的新对象并执行您的复杂操作。我认为使用ThreadLocal 会使代码更难理解。
      【解决方案3】:

      说到测试,最好的工具是依赖注入。它允许将假依赖注入到被测对象中。

      所有依赖注入框架(Spring、CDI、Guice)都有范围的概念(其中 request 是这些范围之一)。在后台,存储在请求范围内的 bean 确实与一个 ThreadLocal 变量相关联,但这都是由依赖注入框架完成的。

      因此,我要做的是使用 DI 框架,它可以使请求范围的对象在任何地方都可用,但不必查找它们,这会破坏可测试性。只需在您想使用它的地方注入一个请求范围的对象,DI 框架就会为您检索它。

      【讨论】:

      • 我已经熟悉 DI 框架并在这种情况下使用 spring,但从未想过为此目的使用 request-scoped bean,它打开了新的视角。我只是后悔不得不多次注射豆子。由于我公司目前的政策是不使用注释而是使用配置文件,它会在 xml 中添加大量洪水
      • 此外,我可以轻松地在过滤器中声明一个 ThreadLocal,我将在我的 web.xml 中声明,但是如何在过滤器中注入一些东西呢?因为我的应用程序是 JSF,所以我的方法是从很多点调用的,我看不到在哪里初始化该请求范围对象,而是在请求过滤器中
      • 关于 XML:我能感觉到痛苦。基于注释的 DI 要好得多。关于过滤器,您可以使用DelegatingFilterProxy,或将过滤器替换为HandlerInterceptor。但我再说一遍:你不需要任何 ThreadLocal。只是一个请求范围的bean。 ThreadLocal 的脏处理将由 Spring 处理。
      【解决方案4】:

      您必须知道 servlet 容器可以/将重用线程来处理请求,因此如果您确实使用 ThreadLocals,则需要在请求完成后自行清理(可能使用过滤器)

      【讨论】:

      • 这已经计划好了。事实上,如果我不这样做,我非常担心潜在的内存泄漏,但这是更好的理由
      • 使用 ThreadLocal 您可能会泄漏数据,而不是内存。
      【解决方案5】:

      如果您是项目中唯一的开发人员,并且您认为自己有所收获:那就去做吧!因为这是你的时间。但是,请准备好在以后恢复决定并重新组织代码库,这应该始终如此。

      假设该项目有十名开发人员。每个人都可能希望有它的线程局部变量来传递货币、语言环境、角色等参数,甚至可能变成一个 HashMap....

      我认为最终,并不是所有可行的事情都应该做。复杂性会反击你....

      【讨论】:

        【解决方案6】:

        如果我们在超出范围后不手动设置 null,ThreadLocal 可能会导致内存泄漏。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2023-03-19
          • 1970-01-01
          • 2021-08-30
          • 2019-09-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多