前几天在定位一个中文乱码的问题,尝试使用了几种方式,包括设置tomcat server.xml中的属性URIEncoding为UTF-8、在项目web.xml文件中使用Spring的CharacterEncodingFilter设置属性encoding为UTF-8作为整个项目的filter,甚至查看了Linux的环境变量(UTF-8)编码,也无济于事,这个问题怪异的地方在于:这个搜索接口的中文字符的编解码方式代码已经很久没有动过了,按道理来说不应该会出现问题,生产环境中中文搜索也没有出现问题,偏偏在测试环境出现了中文乱码的问题,一时之间毫无思路。
查看代码时发现如下的代码片段:
String word = request.getParameter("keyword");//keyword=中文
String keyword = new String(word.getBytes("ISO-8859-1"),"UTF-8");
调试发现keyword的值为 ?? ,很明显是因为编码不对导致乱码,断点调试发现变量word值已为"中文",根本不需要再做编码转换,当然,要解决当前环境的乱码问题已经很简单了,就是直接去掉不需要的编码转换逻辑即可,但是考虑到生产环境中这种逻辑是没有问题的,中文字符并没有出现乱码的情况,一时之间不敢乱改,后来咨询了组内的同事,他们建议再观察下,可以加上一个开关来控制是否需要做编码转换,虽然这种方式有点挫,但是当时考虑到版本快要上线了,时间紧急,实属无奈之举,在编码转换前加了一个开关控制的逻辑,代码片段类似这种:
if(isEncodeOpen())
{
keyword = new String(word.getBytes("ISO-8859-1"),"UTF-8");
}
上线时开关是开启的,即是允许做编码转换的,怀着忐忑的心情等待版本上线,上线后验证了下相关的搜索接口,发现一切正常,中文搜索没有问题!!!
一句话总结就是:真是奇了怪了。。。但是因为当时比较忙,看着相关功能正常也就没有太多去关注这个问题了,但是总是有一种如鲠在喉的感觉。
这两天终于有空可以回溯下这个问题的根源了。
乱码产生的原因:解码时采用的encoding和编码时采用的encoding不一致造成的
这里有两个问题:
1. 为啥在做编码转换时写死由ISO-8859-1转换为UTF-8?
2. 同样的代码,为啥生产环境没有问题,测试环境会出问题?
排查原因时主要分析了以下三个方面:
1. 项目是否使用了统一的编码filter
2. 搜索接口是否设置了单独的编解码方式
3. tomcat的版本以及server.xml文件的配置
关于第一点,项目设置统一编码filter的问题,在项目的web.xml中是有设置统一的编码filter:CharacterEncodingFilter为UTF-8
因此排除第一个问题的疑问。
同样在查看搜索相关的代码时也没有发现有针对搜索接口做单数的编码设置,类似request.setCachacterEncoding("ISO-8859-1")之类的代码。
针对第三个问题,当在百度和Google搜索中文乱码问题时,大部分的答案都是说在tomcat的server.xml文件中设置URIEncoding=UTF-8即可解决乱码的问题,尝试过后无果,依旧乱码,后来尝试去比较了下生产环境跟测试环境server.xml文件的配置,也没有发现有编码设置相关的差异,但是在比较过程中发现了一个很重要的不同--tomcat版本不一致,生产环境的tomcat版本是7.x的版本,而测试环境的tomcat版本是8.x版本。
顺着上面的思路,既然网上有人说设置URIEncoding可以解决中文乱码的问题,那么就要看下tomcat7.x跟8.x版本对该属性URIEncoding是否存在不一样的地方,如果不设置,默认的值是否不一样?
首先先看下是如何设置URIEncoding属性的:
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
URIEncoding="UTF-8"
/>
查看tomcatMigrating from 7.0.x to 8.0.x官方文档时发现关于URIEncoding的相关说明:
Default URL encoding
The default value of URIEncoding attribute for HTTP and AJP connectors has been changed from "ISO-8859-1" to be "UTF-8" (if "strict servlet compliance" mode is off, which is the default). This setting specifies what character encoding is used to decode '%xx'-encoded bytes in path and query of a request URI.
If server is configured with "strict servlet compliance" on, the default value of URIEncoding attribute of connectors is "ISO-8859-1", the same as in older versions of Tomcat.
大意是tomcat由7.x版本升级到8.x版本时,默认的URL编码方式由ISO-8859-1变为UTF-8,看到这里应该可以解决这次有关中文乱码的疑问了。
更详细的来看下tomcat官方文档在7.x和8.x版本对URIEncoding属性的说明:
8.x版本:tomcat 8.x版本 URIEncoding
URIEncoding
This specifies the character encoding used to decode the URI bytes, after %xx decoding the URL. If not specified, UTF-8 will be used unless the org.apache.catalina.STRICTSERVLETCOMPLIANCE system property is set to true in which case ISO-8859-1 will be used.
大意是如果不设置属性org.apache.catalina.STRICTSERVLETCOMPLIANCE=true,那么默认的编码方式会是UTF-8。
7.x版本:tomcat 7.x版本 URIEncoding
URIEncoding
This specifies the character encoding used to decode the URI bytes, after %xx decoding the URL. If not specified, ISO-8859-1 will be used.
大意是如果不设置的话,那么默认的编码方式会是ISO-8859-1.
看到这里,我想我上面提出的两个问题已经可以很好的解释了.
1.代码写死ISO-8859-1转换为UTF-8,是因为tomcat7.x版本默认的编码方式ISO-8859-1,因此需要做相应的转换
2.同样的代码,生产环境没有问题,测试环境有问题,是因为测试环境升级了tomcat的版本为8.x版本,而生产环境未做升级,仍为7.x版本,两个版本关于URIEncoding属性的默认值不一样,7.x默认是ISO-8859-1, 8.x默认是UTF-8。
现在想想,当初加开关是正确的选择,可以完美解决tomcat版本不一致带来的编码问题。。。哈哈哈
不过仍然有很多值得总结的地方:
1.环境不一致:测试环境跟生产环境tomcat版本应该要保持一致,可以及时发现和预防tomcat版本升级带来的问题,当然也不仅仅是tomcat的版本,包括MySQL、JDK等的版本都应该保持一致。
2.代码写死编码转换方式,这种做法并不是很好,应该通过配置文件的方式来配置相关的属性值,可以通过tomcat容器的配置或者项目自身的配置来实现。
3.版本升级一定要看官方文档说明,看是否存在升级指导、changlog之类的文档,有问题查看官方文档才是正解。
4.关于编码,最好统一设置成UTF-8,不要采用默认的编码方式,显式的将编码方式设置成UTF-8,这样就不会因为版本升级或者环境变更带来编码的问题,同时也不会出现上面那种ISO-8859-1转换为UTF-8编码的代码了~