【问题标题】:Is it OK to use ThreadLocal for storing the requested Locale?可以使用 ThreadLocal 来存储请求的 Locale 吗?
【发布时间】:2013-05-26 04:56:35
【问题描述】:

我正在对存储在数据库中的相当大的客户端/服务器(HTTP (Hessian) 用于通信)应用程序中的用户输入数据进行国际化。用户可以选择他们想看的语言,当请求的语言翻译不存在时,会使用默认语言。

目前一个数据类可能是这样的:

class MyDataClass {
  private Long id;
  private String someText;
  /* getters and setters */
}

国际化后可能如下所示:

class MyDataClass {
  private Long id;
  private Set<LocalizedStrings> localizedStrings;
  /* getters and setters */
}
class LocalizedStrings {
  private Locale locale;
  private String someText;
  /* getters and setters */
}

当然,在 MyDataClass 中创建一个委托 getter 可能会很有趣,它负责在正确的语言环境中获取文本:

public String getSomeText(Locale locale) {
  for(LocalizedString localized : localizedStrings) {
    if (localized.getLocale().equals(locale)) {
      return localized.getSomeText();
    }
  }
}

在我的团队中,有一些担心需要一直传递语言环境,直到他们到达数据类。由于所有这些事情都发生在服务器上,并且对服务器的每个请求都在专用线程中处理,因此有人建议将请求的语言环境存储在 ThreadLocal 对象中并创建一个向后兼容的无参数 getter:

public String getSomeText() {
  return getSomeText(myThreadLocalLocale.get());
}

然后,ThreadLocal 需要是一个全局变量(在某处是静态的),或者需要在每次创建单个实例时将其注入 MyDataClass(我们使用的是 spring,因此如果我们使我们的数据类受 spring 管理,我们可以注入它(其中我觉得不对))。

在语言环境中使用 ThreadLocal 对我来说是错误的。我可以含糊地说,我不喜欢 getter 中的隐形魔法和对全局变量的依赖(在数据类中!)。然而,对此有“不好的感觉”并不是与我的同事争论的好方法。为了帮助我,我需要以下其中一项的答案:

  1. 告诉我,我的感觉很糟糕,解决方案很好,原因是 X、Y 和 Z。
  2. 给我一些很好的可引用论据,我可以用来与同事争论,并告诉我如何做得更好(只是总是传递语言环境或任何其他想法?)

【问题讨论】:

  • 为什么localizedStrings 使用 Set 而不是 Map?
  • 您是否考虑过使用 Spring LocaleContextHolder。它以自己的方式存储线程的当前语言环境(由 DispatcherServlet 存储)。
  • 如果您的本地化变量的范围是单线程,那么这似乎是一个聪明(且简单)的解决方案。如果您要冒险进入多个线程,那么显然,这需要更多的东西。如果您的 API 在每个请求中都有本地化信息,那么它是一个完全有效的解决方案。 KISS,这是你能提出的最好的论点。
  • @JimmyT.:因为与休眠映射有关的原因。另一个有趣的问题,但不是这里问题的一部分。
  • 但是hibernate很好的支持Maps。

标签: java thread-local


【解决方案1】:

虽然我不喜欢在应用程序中“深入”地进行本地化。

在此:

public String getSomeText() {
  return getSomeText(myThreadLocalLocale.get());
}

我们这样做:

public LocalizableText getSomeText() {
  return new LocalizableText(resourceBundle, "someText");
}

然后做,例如在 JSP 或输出层中:

<%= localizable.getString(locale) %>

逻辑本身与语言无关。在某些情况下,经过一些处理后,应用程序通过邮件发送结果、记录并以各种不同的语言呈现给 Web 用户。因此,处理与结果生成和本地化必须分开。

【讨论】:

    【解决方案2】:

    这种方法是完全有效的。 例如,Spring 通过RequestContextListenerLocaleContextHolder 使用ThreadLocal 提供Locale。

    如果您创建自定义实现,请确保正确处理 ThreadLocal(设置/删除)。

    【讨论】:

    • 请注意,此解决方案要求您确保在切换到另一个线程时复制您的线程本地。像 myCompletableFuture.thenRunAsync(...) 这样简单的东西会让你失去你的线程本地人。更糟糕的是,您可能会使用其他一些也执行 RunAsync 或使用 java.nio.* 或基本上任何异步代码的请求来切换线程本地。
    【解决方案3】:

    使用您所描述的本地线程是 Web 应用程序中非常常见的模式。以 Spring API 中的这个类为例:

    org.springframework.web.context.request.RequestContextHolder
    

    使用 servlet 过滤器(或类似过滤器)在本地线程中设置区域设置,然后在服务器完成每个请求后清除区域设置值。不要在每个使用它的地方注入它,而是使用类似于 RequestContextHolder 的静态工厂/访问器方法:RequestContextHolder.getRequestAttributes()。

    【讨论】:

      【解决方案4】:

      ThreadLocal 是不好的做法。它是全局变量,任何语言都有很多关于这有多糟糕的文章。 Spring 使用它的事实并不能证明使用它是合理的。我喜欢 cruftex 给出的解决方案。避免通过全局变量传递数据。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-06-29
        • 2014-03-29
        • 1970-01-01
        • 2019-12-26
        • 2020-07-21
        • 2011-02-15
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多