前提条件
在解决问题之前,web模块中配置了自定义的HttpEncodingFilter和GetHttpServletRequestWrapper,期待能够解决所有服务器上的中文乱码问题,很遗憾,让大家失望了。最后给出web.xml中过滤器和两个类的明细。
在某个功能中,form表单通过post提交,表单中大部分参数通过struts1的form.vo(包含中文)封装,另外两个参数(包含中文)通过表单域直接提交,格式大概就是这样子了:
<form action="site.do" method="post">
<input name="vo.name" value="黄金沙" /> //注意跟strtus form关联
<input name="authValue" value="系统管理员" />
</form>
这样在SiteAction中,根据vo获取name的值不会乱码,通过request.getParameter("authValue")就可能出现乱码(随服务器而定),经过测试在jboss和weblogic下面,不出现乱码,在was下面乱码。
分析过程
由于代码使用了框架struts1,表单提交时,vo.name经过struts的处理,authValue是不经过处理的,这也就造成应用服务器对这两个参数的不同处理。
首先让我们看下HttpEncodingFilter这个类:
public class HttpEncodingFilter implements Filter {
private String charset = "UTF-8";
private String uriEncoding = "iso8859-1";
private FilterConfig config;
private Logger logger = Logger.getLogger(this.getClass());
public void doFilter(javax.servlet.ServletRequest request,
javax.servlet.ServletResponse response,
javax.servlet.FilterChain chain) throws java.io.IOException,
javax.servlet.ServletException {
response.setCharacterEncoding(charset);
// 新增加的代码 (仍然存在问题,当使用multipart提交时,如果参数写在表单中会出现中文乱码) TODO
HttpServletRequest req = (HttpServletRequest) request;
boolean hasEncoding = false;
if (req.getCharacterEncoding() != null) {
hasEncoding = true;
}
if (!hasEncoding) {
req = new GetHttpServletRequestWrapper(req, charset, uriEncoding);
}
chain.doFilter(req, response);
};
public void init(FilterConfig config) throws ServletException {
this.config = config;
String charset = config.getInitParameter("charset");
if (charset != null && charset.trim().length() != 0) {
this.charset = charset;
}
String uriEncoding = config.getInitParameter("uriEncoding");
if (uriEncoding != null && uriEncoding.trim().length() != 0) {
this.uriEncoding = uriEncoding;
}
}
public void destroy() {
// TODO Auto-generated method stub
}
}
在这个过滤器中,没有设置request的CharacterEncoding,设置了response的CharacterEncoding为GBK,但是用自定义的GetHttpServletRequestWrapper封装了ServletRequest,可以看下这个类怎么写的:
package com.excellence.exportal.base.common;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.log4j.Logger;
public class GetHttpServletRequestWrapper extends HttpServletRequestWrapper {
private String charset = "UTF-8";
private String uriEncoding = "iso8859-1";
private String Method = "GET";
private boolean isAjax = false;
private Logger logger = Logger.getLogger(this.getClass());
public GetHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
/**
* 获得被装饰对象的引用和采用的字符编码
* @param request
* @param charset
*/
public GetHttpServletRequestWrapper(HttpServletRequest request,
String charset,String uriEncoding) {
super(request);
this.charset = charset;
this.uriEncoding = uriEncoding;
}
/**
* 获得被装饰对象的引用和采用的字符编码
* @param request
* @param charset
*/
public GetHttpServletRequestWrapper(HttpServletRequest request,
String charset,String uriEncoding,String method,boolean isAjax) {
super(request);
this.charset = charset;
this.uriEncoding = uriEncoding;
this.Method = method;
this.isAjax = isAjax;
}
/**
* 实际上就是调用被包装的请求对象的getParameter方法获得参数,然后再进行编码转换
*/
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
value = (value == null ? null : convert(value));
return value;
}
@Override
public String[] getParameterValues(String name){
String[] resultArr = super.getParameterValues(name);
if(resultArr == null || resultArr.length <= 0){
return null;
}
for(int i=0;i<resultArr.length;i++){
String temp = resultArr[i];
if(temp!=null){
resultArr[i] = convert(temp);
}
}
return resultArr;
}
@Override
public Map getParameterMap() {
//在高版本的api中 map的value是string数组,而低版本的value是string,故使用'?'号占位
Map<String,?> paramMap = super.getParameterMap();
Object value = null;
for(Map.Entry entry:paramMap.entrySet()){
value = entry.getValue();
if(value!=null){
if(value instanceof String[]){
String[] values = (String[])value;
for(int i=0;i<values.length;i++){
values[i] = convert(values[i]);
}
entry.setValue(values);
}else{
entry.setValue(convert(value.toString()));
}
}
}
return paramMap;
}
public String convert(String target) {
try {
byte bytes[] = target.getBytes(this.uriEncoding);
String result = new String(bytes,this.charset);
return result;
} catch (UnsupportedEncodingException e) {
return target;
}
}
}
在这个类中,重写了getParameter、getParameterValues、getParameterMap这些方法,在这些方法里面都实现了将value按指定编码转码的逻辑。
继续回到表单提交的情况,上文提到了struts的表单提交与普通表单域处理中文时,不同表现(乱码和不乱吗),这块也反复提到跟应用服务器对请求参数的处理分不开,结合GetHttpServletRequestWrapper来讲。
其中struts中表单提交在jboss、was、weblogic中的表现一致,均不会出现乱码,具体原因的话,struts将浏览器端提交的参数,不经转码直接交给GetHttpServletRequestWrapper的getParameterValues方法处理,其中post提交的时候,在页面已经制定contextType是GBK的编码,这样getParameterValues拿到编码后的值,经过一层转码,得到正常的中文值,所以用struts提交始终不出现乱码的情况(前提post方式提交表单)。
但是authValue的值获取就不一样了,在action中通过getParameter API获取相应的值,由于GetHttpServletRequestWrapper封装了实际的request,这里调用的是GetHttpServletRequestWrapper中的getParameter方法,细细看这个方法发现,里面会对拿到的value进行一层转码。问题就出来了,如果转码后出现乱码,只能说明转码前的内容不是提交后浏览器编码的内容(有点绕口)。这里需要解释一个常识,post提交方式,浏览器会根据jsp页面的contextType的编码对表单值进行编码。经过进一步的对比分析,发现在was(8.5版本)下面运行这段代码,出现中文乱码,在weblogic(10.3版本)下面运行,正常。
简单描述这个问题:在浏览器提交post请求后,对于getParameter的API,weblogic不会根据处理编码;而was则会进行一次转码(可能是根据jvm的编码进行转码),这样GetHttpServletRequestWrapper的getParameter API必然会出现乱码了。
解决办法
1、针对应用服务器给出不同的解决方案,在代码层面做适配,缺点,过滤器显得不够通用。
2、从根本上解决乱码问题,不传中文,实在要传中文的地方,在提交请求前,将中文进行转码,常用的转码方案,利用javascript的encodeURIComponent编码,根据请求类型get 或者 post 多次使用encodeURIComponent。
这里给出我们用的一种解决方案,将中文用base64编码,提交后,在后台用base64解码,这样的好处,可以避免浏览器对表单值就行默认的编码处理。但是注意一个问题,注意base64解码时的程序的健壮性处理,因为某些情况写,由于解码出现异常,这是正常的。
参考文档
http://www.cnblogs.com/gywbg/archive/2012/04/13/2445634.html
http://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/
利用base64解决传参的编码问题,这个主意非我所想,感谢团队的小伙提供这个方法,我只是代劳整理了出来。
来源:oschina
链接:https://my.oschina.net/u/781254/blog/508302