如何为 tomcat 中的每个虚拟主机设置时区

How set TimeZone for each virtual host in tomcat

我有 3 个地区(蒙古、土库曼尼亚等)的网络应用程序。它们部署在tomcat的虚拟主机上。现在我需要为每个应用程序设置 timezone。我怎样才能做到这一点? 我为每个应用程序实现了 ServerContextListener 接口来设置 TimeZone:

@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
    TimeZone timeZone=TimeZone.getTimeZone("Asia/Ulan_Bator");
//  TimeZone timeZone=TimeZone.getTimeZone("Asia/Ashgabat");
    timeZone.setDefault(timeZone);
}

但部署后每个应用程序都有相同的时区。

P.S:对不起我的英语:)

您可以为 tomcat 设置 JVM 时区。为此,您需要在 tomcat 配置文件中的 JVM 选项中添加以下选项。

-Duser.timezone=UTC

使用您的时区代替 UTC。它将在该时区启动您的 JVM。

ZoneId,不是TimeZone

可怕的遗产 date-time 类 多年前被现代的 java.time 类 取代,采用了 JSR 310.

TimeZone 替换为 ZoneId & ZoneOffset

ZoneId z = ZoneId.of( "Asia/Ulan_Bator" ) ;

避开默认时区

避免依赖 JVM 当前的默认时区。

JVM 只有一个 个默认时区。调用 TimeZone.setDefault 会立即影响该 JVM 中所有应用程序的所有线程中的所有代码。

But after deploy each apps has same TimeZone.

因此您不能为每个网络应用程序设置不同的默认时区。如果所有 Web 应用程序都 运行 在同一个 Servlet 容器中,那么所有 Web 应用程序共享相同的默认时区。

传递所需的时区

更好的方法是将 desired/expected 时区作为可选参数显式传递给各种 java.time 方法。例如,如果您想捕捉在特定时区看到的当前时刻,请将所需的 ZoneId 对象传递给 now 方法。

ZoneId z = ZoneId.of( "Asia/Ashgabat" ) ;
ZonedDateTime zdt = ZonedDateTime.now( z ) ;

与其依赖于 JVM 当前的默认时区,不如为您的每个 Web 应用程序设置一个时区。

ServletContextListener::contextInitialized

您已接近解决方案。当每个 Web 应用程序启动时,您需要指定其默认值。这样做的地方是在 contextInitialized method in your class implementing the ServletContextListener 中,如您的问题所示。

在该方法中,指定所需的时区。

ZoneId z = ZoneId.of( "Asia/Ashgabat" ) ;

但是该变量超出范围并在 contextInitialized 方法结束时消失。所以我们需要在某处存储对该 ZoneId 对象的引用。但是哪里? Servlet 规范为您在应用程序中跨代码所需的此类“全局”变量或常量定义了一个位置。

您的 contextInitialized 方法被传递了一个 ServletContextEvent. That object holds a reference to the ServletContext 对象,代表正在启动的网络应用程序的当前实例。

ServletContext myWebAppContext = servletContextEvent.getContext() ;

属性(key-value 存储)

ServletContext 方便地维护一个简单的 key-value 存储“属性”。键是 String,值是 Object。我们可以为我们的 per-web-app 默认时区创建一个字符串名称,并将 ZoneId 作为 Object 值传递。将这些传递给 ServletContext::setAttribute.

String key = "com.example.acme_webapp.zoneid" ;
Object value = ZoneId.of( "Asia/Ashgabat" ) ;
myWebAppContext.setAttribute( key , value ) ;

在您的应用程序代码中,look up the context 并在您需要时随时添加属性。询问正在服务的 Servlet 请求的上下文。

ServletContext context = request.getServletContext() ;

从存储的属性中请求值。

Object value = context.getAttribute( "com.example.acme_webapp.zoneid.mongolia" ) ;

Object 转换回 ZoneId。我们知道这应该有效,但对于防御性编程,您可能需要检查 instanceof

ZoneId z = ( ZoneId ) value ;

然后继续你的工作。正如我们上面所讨论的,也许您想捕捉那个区域中看到的时间。

ZonedDateTime zdt = ZonedDateTime.now( z ) ;

提示:您可能想要定义一个枚举来处理属性名称的多个区域字符串。 Copy-pasting 字符串值是风险业务。参见 tutorial

Thread-safety

请注意,虽然每个网络应用程序实例都有自己的 ZoneId object, that object is being shared across threads. A Servlet environment is by definition highly-threaded, each request being serviced on a thread. So that ZoneId is being shared across any number of threads. But that is okay. Fortunately, the java.time classes are thread-safe by design, using the immutable objects 模式。

强制性 Servlet 线程并发提示:任何进行 Servlet 编码的人都应该阅读 re-reading 这本书:Java Concurrency in Practice 作者:Brian Goetz、Tim Peierls、Joshua Bloch、Joseph Bowbeer、David Holmes、Doug Lea .