Tomcat10.1.x发布,告别中文乱码

704 阅读3分钟

大多数亚洲的开发者,例如中国、俄罗斯、日本、韩国,我们使用Apache Tomcat时遇到的第一个问题,通常就是乱码,无论是在Windows还是Linux,无论是Console中的Log还是Browser中的JSP,都存在编码错误的问题。Tomcat让我们亚洲开发人员认识了一种在其他平台闻所未闻的特殊编码,名为"ISO 8859-1"的西欧语字符集,西欧语不适用于亚洲开发人员,通常都要对其进行一些设置,例如server.xml中设置URIEncoding属性,Servlet中设置setCharacterEncoding,在logging.properties中设置java.util.logging.ConsoleHandler.encoding,这些四处杂乱无章的设置令人困扰。其实这是一个古老的问题,从Tomcat十多年前创建之初就是如此,经常是Tomcat对国际化的支持有问题吗?

确实如此,Tomcat一直到2018年11月16日才在9.0.14版本中进行了中文以及其他流行语言的支持,制作了数个LocalStrings_zh_CN.properties文件commit↑,假如你用的Tomcat8以及以下版本,那Tomcat他就是不支持中文的。但我们其实不需要其对中文的支持,只需要他依照正常的情况去处理中文字符,这就足够了,但Tomcat却没能把这一点做好,还产生了很多疑问:

  1. 为什么Tomcat要使用奇怪的"ISO 8859-1"西欧语字符集? 最初这是Tomcat开发者们的原因,开发者们都来自西欧,自然使用西欧字符集,那时候UTF-8并不普遍。也有Java的原因,Tomcat由于开发时间非常的早,酷爱原生的Java包,其中就包括一个万恶之源java.util.ResourceBundle,它是一个用作国际化处理的包,但问题就在于这个国际化包,竟将所有的字符都转为ISO-8859-1,一直到Java9才改正这个可怕的错误。所幸的是由于ResourceBundle的接口设计也非常糟糕,很少有开发人员使用它;不幸的是Tomcat在项目之初就使用了ResourceBundle,至今仍在大量使用,埋下了编码错误的祸根。

  2. 如何找到ResourceBundle的错误 只需要一份中文的demo_zh_CN.properties文件正常使用即可

#demo_zh_CN.properties
text=中文
//Main.java
ResourceBundle bnd = ResourceBundle.getBundle("demo", Locale.CHINA);
System.out.println(bnd.getString("text"));

输出为

中文

这段ISO-8859-1编码转换一下即可正常显示,只需要:

System.out.println(new String(bnd.getString("text").getBytes(Charset.forName("ISO-8859-1")), Charset.defaultCharset()));
中文

但每次取值都进行这样的转换是无法接受的

  1. 如今在Tomcat9已经出现了一种配置无法改变的编码错误,就是在浏览器上包含状态码的默认报错页,例如最常见的400、404、500,依照上文的设置方式都没有任何的作用,这是为什么?

只能通过Chrome的语言设置项将英文设置为优先解决此问题,或者在页面上也尝试使用ISO 8859-1编码,或者自己编写错误页以替代Tomcat。如果Chrome语言设置中文优先,首先Tomcat根据Accept-Language取出浏览器请求的语言,然后使用ResourceBundle寻找中文配置项,读取到LocalStrings_zh_CN.propertiesResourceBundle就已经将其转为"ISO 8859-1",Tomcat并没有像上面那样进行编码转换,生成错误页时取出错误文本的代码在ErrorReportValve.java#L222,Tomcat对ResourceBundle进行了封装,获取bundle值的代码在StringManager.java#L133,整个错误页拼接过程几乎都不受到任何字符集配置,参数的影响,所以只能修改Tomcat源码重新编译以解决。

  1. Tomcat10.1.x修正了此bug吗?

没有,但是Java修复了此bug,Java9中ResourceBundle会默认转换为UTF-8,Tomcat10.1.x使用了Java 11,而Tomcat10.0仍然使用Java 8,所以Tomcat10.1.x不再存在这个问题,感谢Oracle终于发现并修复了这个问题:

docs.oracle.com/javase/9/in…