前提条件
在解决问题之前,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解决传参的编码问题,这个主意非我所想,感谢团队的小伙提供这个方法,我只是代劳整理了出来。