Web开发的中文问题一直困惑大家,尤其是对于初上手者。这次有机会彻底解决研究了一下中文乱码的原因和解决方案,做个总结。
为什么会有中文乱码?
因为在默认情况下,HTTP的包都是以“8859_1”来编码的(没办法,谁叫这些标准都是老美定的)。“8859_1”是西文编码方式,对于英文字母没有任何问题,但是对于中文就不行了。所以,如果不做任何设定,直接将中文用“8859_1”来编码传递,那结果必然是乱码。
解决思路是什么?
好在老美还是有国际化眼光的,HTTP包的编码方式可以由用户指定。因此,只要事先指定好用相对应的编码方式来对传递内容(比如表单提交的中文等)进行编码,就可以顺利解决乱码的问题。
两个基本概念
在进入具体的解决方法之前,首先要对两个基本概念作一下解释。对于由表单提交的内容,HTTP有两种传递方式,分别是“GET”方式和“POST”方式。
“GET”方式就是将各参数直接通过HTTP的包头(head)来传递,简而言之就是直接通过我们所熟悉的网址(URL)来传递,所以我们经常能看到的在一个网址后面跟着许多复杂的由“?”和“&”构成的字符串,其实这就是需要传递的参数了。
“POST”方式则是将所需传递的参数包在HTTP的正文(body)中来传递。因此通过“POST”方式来进行传递,在浏览器的网址上面什么都看不见。
因此,相比较而言,“POST”隐蔽性较好;而“GET”方式使用起来比较容易,直接写URL就可以了。
综上所述,不难发现,解决中文乱码问题实际上就变为对这两种HTTP传递的编码方式进行适当的设定。当然,从解决问题的难易以及对系统架构的完美性角度着手,又分为以下三个层次:
1)入门方法,在所有的servlet和jsp中堆设定用的代码。
2)中级方法,对web伺服器进行配置。
3)高级方法,编写filter过滤器,对“POST”和“GET”独立过滤处理。
下面就具体描述各解决方法:
1)入门方法,在所有的servlet和jsp中“堆”写设定用的代码。
所谓入门方法,那就是现实十分简单,当然效果也是很好的。只是必须在每个相应的文件中写相同的设定代码,代码的重复性就比较大。
由前面所述,由于“POST”和“GET”方式的不同,因此对应着两种的设定方式也不同。
“POST”的情况下,如果服务器端脚本是一个servlet,那只要在doPost()方法里面插入一句
request.setCharacterEncode("GB2312");
需要注意的是,这句设定必须在所有从request对象做提取操作之前执行,如果类似于request.getParameter()的操作在前,那么系统将使用默认的“8859_1”编码方式,而忽略后面的设定代码。
如果服务器端是一个jsp脚本,那只要在该脚本的jsp申明部分做好设定即可:
<%@ page language="java" contentType="text/html; charset=gb2312" pageEncoding="gb2312"%>
如果是“GET”方式,也就是想通过URL来传递中文的话,稍微要麻烦些,首先因为浏览器地址栏是不支持中文的,也就是如果直接将中文放置在超级连接里面是无效的。因此需要在发送端对中文内容进行编码,比如:
URLEncoder.encoder("http://localhost/submit?name=张三","UTF-8");
“UTF-8”表示用这种编码方式对原字符串进行编码,编码好之后看到的结果是
http://localhost/submit?name=%D5%C5%C8%FD
所以我们经常看到在浏览器里面有众多的类似与“%D5%C5%C8%FD”这样的字符串,就是表明被UTF-8编码过了。由于UTF-8是跨各种平台的通用编码方式,因此比较常用于各种语言文字的传输载体。
相对应的,在接受方需要进行反向的解码即可,代码如下:
new String( request.getParameter("name").getBytes("8859_1"), "gb2312" );
这里可能会有一些疑问,为什么用“8859_1”来解码。事实上,我在第一次尝试的时候也曾使用“UTF-8”来尝试解码,结果出现乱码失败。究其原因,尽管“张三”被编码成了“%D5%C5%C8%FD”来传输,但是在传输过程中,“%D5%C5%C8%FD”仍旧需要由“8859_1”来编码打包成HTTP,因此,在接收端,自然先需要由“8859_1”来还原到“%D5%C5%C8%FD”的“UTF-8”格式,然后再由“UTF-8”还原到“GB2312”。
所以这样也不难理解为什么所谓“浏览器地址栏是不支持中文”,不能直接用中文而要用“UTF-8”来通过“8859_1”来打包了,原因就是“%D5%C5%C8%FD”这串类似于密码般的字符串本身就是西文字符,用“8859_1”编解码没有任何问题。而中文由于是2byte一个汉字,直接用西文方式来编解码自然就会出现问题。这也就是为什么称“UTF-8”为“跨各种平台的通用编码方式”了。
背景小资料:
由于“UTF-8”是通用编码方式,因此所有的语言格式均可以转换为“UTF-8”,在日益国际的今天,多语言的系统要求越来越多,因此强烈建议使用“UTF-8”来做为系统统一的编解码方式,从而彻底解决中文乱码的问题。
“UTF-8”为了能做到兼容所有语言的编解码,因此每一个字符均用2个byte来编码。这样就造成了存西文字符时需要多一倍的空间。这也算是为了通用而付出的代价了。
2)中级方法,对web伺服器进行配置
可想而知,相对于“堆”写大量代码,配置一下web伺服器config文件来解决中文乱码问题就显得优雅许多。但是由于各种web伺服器的情况不同,其配置方法也不尽相同。因此,其兼容性是个比较大的问题。
这里列举一下,如何通过修改Tomcat的conf配置文件来解决中文乱码的问题。
找到Tomcat的配置文件server.xml中的Connector这一行,为其添加一个如下的属性
URIEncoding = "GB2312"
这样就指定了使用“GB2312”来进行编解码。不过需要注意的是,tomcat4.x以以前的版本由于蒋“POST”和“GET”等同视之,因此这样一句设定就可以适用于两种方法。而到了tomcat5.x以后,两种方式就分开处理了。因此在tomcat5.x的情况下,只做这个设定,那仅仅对“POST”方式有效,“GET”方式仍然会得到乱码。
不过好在tomcat5.x考虑到了这个问题,提供了一个附加的参数:
useBodyEncodingForURI = "true"
如果做了这样的设定,那5.x就将兼容4.x而“POST”和“GET”等同视之。
3)高级方法,编写filter过滤器,对“POST”和“GET”独立过滤处理。
高级方法,顾名思义,就是可以脱离于任何平台,同时又免去冗余的队旗代码工作的解决方案——编写过滤器,Filter。
首先编写一个过滤器SetCharacterEncodingFilter
public class SetCharacterEncodingFilter implements Filter {
/**
* The default character encoding to set for requests that pass through
* this filter.
*/
protected String encoding = null;
/**
* The filter configuration object we are associated with. If this value
* is null, this filter instance is not currently configured.
*/
protected FilterConfig filterConfig = null;
/**
* Should a character encoding specified by the client be ignored?
*/
protected boolean ignore = true;
// --------------------------------------------------------- Public Methods
/**
* Take this filter out of service.
*/
public void destroy() {
this.encoding = null;
this.filterConfig = null;
}
/**
* Select and set (if specified) the character encoding to be used to
* interpret request parameters for this request.
*
* @param request The servlet request we are processing
* @param result The servlet response we are creating
* @param chain The filter chain we are processing
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet error occurs
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
// Conditionally select and set the character encoding to be used
if (ignore || (request.getCharacterEncoding() == null)) {
String encoding = selectEncoding(request);
if(encoding != null){
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
if(httpServletRequest.getMethod().toLowerCase().equals("post")){
//如果是POST方法
request.setCharacterEncoding(encoding);
}
else{
//如果是GET方法
//非常抱歉,我还有没有找到很好的对应get方法的代码
//一旦完成了这部分代码,马上添加在这里。
//!·#¥%……—*()——+|
}
}
}
// Pass control on to the next filter
chain.doFilter(request, response);
}
/**
* Place this filter into service.
*
* @param filterConfig The filter configuration object
*/
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
this.encoding = filterConfig.getInitParameter("encoding");
String value = filterConfig.getInitParameter("ignore");
if (value == null)
this.ignore = true;
else if (value.equalsIgnoreCase("true"))
this.ignore = true;
else if (value.equalsIgnoreCase("yes"))
this.ignore = true;
else
this.ignore = false;
}
// ------------------------------------------------------ Protected Methods
/**
* Select an appropriate character encoding to be used, based on the
* characteristics of the current request and/or filter initialization
* parameters. If no character encoding should be set, return
* <code>null</code>.
* <p>
* The default implementation unconditionally returns the value configured
* by the <strong>encoding</strong> initialization parameter for this
* filter.
*
* @param request The servlet request we are processing
*/
protected String selectEncoding(ServletRequest request) {
return (this.encoding);
}
}
编写完过滤器以后,需要对其进行部署,也就是在web.xml中做个配置:
在<display-name>标签之后,添加:
<filter>
<filter-name>Set Character Encoding</filter-name>
<filter-class>com.zavax.utility.filters.SetCharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF8</param-value>
</init-param>
<init-param>
<param-name>ignore</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Set Character Encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>