解决表单提交参数乱码问题【终极版】不看后悔
提交表单乱码问题,一直是困然网站开发人员的“吐血”问题,这问题虽说不痛不痒,但是绝对“恶心人”。之前自己遇到这个问题是,一直是能绕过就绕过,懒得理它,直到今天我又遇到它,我知道,我必须得治治它了。
表单提交通常有两种方式,一种是GET方式,一种时POST方式,两种方式这里就不详细解释了;然后表单参数的传递,也有两种方式,一种是直接把参数加在URL上,以key=value的方式传递,一种是在表单内部添加带name属性的标签,例如input,select标签等。那么它们组合在一起,就有4种方式:
|
URL传参 |
表单标签传参 |
混合传参 |
GET |
A |
B |
C |
POST |
D |
E |
F |
先说一下使用中会出现的问题。A、C方式中,URL上的参数会被表单的参数冲掉,所以A、C方式不要使用。
在说说这几种方式的特点,在GET方式中,表单中所有的参数实际上都是被追加到URL上的(这也是get方式的url传参,url参数被冲掉的原因),表单最后提交给服务器的就是一个url(url长度一般限制为255字符)。这种方式产生的乱码最难缠。
在POST方式中,如果参数位于表单中(等同于ajax提交数据时的data内容),参数是以非url形式提交的,所以这种通常不会出现乱码,而且也容易解决。如果参数位于url中,那参数的传递方式和get方式是一样的,这时产生乱码的原因和get方式是一样的。
现在我们把问题抽象出来了,参数传递有两种,一种是通过url传参,一种是通过data传参。
乱码之所以乱码,是因为编码和解码的格式不一致。
说说我们通常解决乱码的方法。通常有两类解决办法,一类是对参数进行编码,然后后台进行解码,这种方式对于以上几种传参都适用,但是因为前台要编码,后台需要解码,所以增加了代码复杂性。另一种方式就是弄个filter(spring自带一个,就是这货org.springframework.web.filter.CharacterEncodingFilter,可以直接把它配在web.xml里面),对所有请求都setCharactorEncoding()为UTF-8,这种方式通常都行。之所以说通常都行,是因为这种方式之对通过data方式传递的参数有效,对于通过url传递的参数无效,这也是为什么get提交方式产生乱码概率大的原因。
但是我们怎么通过url传参时的乱码呢?也许有人会说,不用url传参不就可以了,但是在许多情况下,我不得不使用url传参,比如一个超链接。
其实只要找到问题所在,解决方案也就好办了。开始时,我的办法是写一个filter,对于通过get方式提交的参数,把所有的参数都进行一下编码转换:ISO-8859-1 à UTF-8。这种方式我使用了很长时间,直到有一次,我不得不使用post方式的混合传参时,才发现url上的参数居然被认为是post方式传递的,当然也没有被我的filter拦截,当然也就乱码了。
不过既然要死磕,就一定要把这问题解决。
思路倒是很清晰,虽然是post方式提交的,但是我们只需要把其中url方式传参的参数进行转码即可(data传参只需要设置CharactorEncoding即可,如果转码那就转成乱码了),可是怎么知道哪些参数是url传递呢?
HttpServletRequest对象有getQueryString()这个方法,这个方法能够获得url传递的参数的字符串,当然了,参数也就包含在其中。所以我们只要把其中的参数名分离出来即可,这些就是我们需要进行转码的,别的不需要解码。
在然后呢,我们通过request获取参数时,一般会通过这么几个方法:getParameter(),getPrarmeterMap(),getParameterValues()这三个方法。所以我们只要在这三个方法上“做手脚”即可。另外,如果某些参数是按照传引用(相对于传值而言,了解c的人,对这个应该比较了解。另外虽然java本质上都是传值,但是如果对象不是基本类型时,就会有传引用的效果)传递的,我们还要设置一些标志位,防止多次转码。
思路已经清楚了,下面直接贴本人的成型代码。
第一个是GetHttpServletRequestWrapper,这个类是“主角”,完成对参数的筛选和转码:
- /*
- * Copyright (c) 2014, ShiXiaoyong. All rights reserved.
- */
- package com.common.filter;
- import java.io.UnsupportedEncodingException;
- import java.util.Enumeration;
- import java.util.HashMap;
- import java.util.Map;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletRequestWrapper;
- /**
- * 描述:GetHttpServletRequestWrapper
- *
- * <pre>
- * HISTORY
- * ****************************************************************
- * ID DATE PERSON REASON
- * 1 2015-3-6 Shixy Create
- * 2 2015-3-23 Shixy 增加对post混合传参方式的支持
- * ****************************************************************
- * </pre>
- *
- * @author Shixy
- * @since 1.0
- */
- public class GetHttpServletRequestWrapper extends HttpServletRequestWrapper {
- private String charset = "UTF-8";
- private static final String ENCODED = "__encoded";
- private Map<String, String> urlParamNames = null;
- /**
- * @param request
- */
- public GetHttpServletRequestWrapper(HttpServletRequest request) {
- super(request);
- initUrlParameterNames();
- }
- /**
- * 获得被装饰对象的引用和采用的字符编码
- *
- * @param request
- * @param charset
- */
- public GetHttpServletRequestWrapper(HttpServletRequest request, String charset) {
- super(request);
- this.charset = charset;
- initUrlParameterNames();
- }
- @Override
- public Enumeration<String> getParameterNames() {
- return super.getParameterNames();
- }
- /**
- * 实际上就是调用被包装的请求对象的getParameter方法获得参数,然后再进行编码转换
- */
- @Override
- public String getParameter(String name) {
- String value = super.getParameter(name);
- // 根据urlParamNames是否包含此值来判断是否需要对其进行get方式转码
- if (!urlParamNames.containsKey(name)) {
- return value;
- }
- if (null != value) {
- value = convert(value);
- }
- return value;
- }
- @Override
- public String[] getParameterValues(String name) {
- // values也是传值
- String[] values = super.getParameterValues(name);
- if ((!urlParamNames.containsKey(name))) {
- return values;
- }
- for (int i = 0; i < values.length; i++) {
- values[i] = convert(values[i]);
- }
- return values;
- }
- @Override
- public Map<String, String[]> getParameterMap() {
- Map<String, String[]> map = super.getParameterMap();
- // 是否已经转码的标识位
- // 因为map是传引用的,因此多次调用时,原值会被转码转码在转码,因此要设置此标志位,防止多次转码
- if ("1".equals(this.getAttribute(ENCODED))) {
- return map;
- }
- // 对map中所有的url传参进行编码
- // 遍历map中的参数,转换器编码
- for (String key : urlParamNames.keySet()) {
- String[] value = map.get(key);
- if (value != null) {
- for (int i = 0; i < value.length; i++) {
- value[i] = convert(value[i]);
- }
- }
- }
- this.setAttribute(ENCODED, "1");
- return map;
- }
- /**
- * 将字符串转码
- * ISO-8859-1为国际通用url编码
- * @param target
- * @return
- */
- private String convert(String target) {
- try {
- return new String(target.trim().getBytes("ISO-8859-1"), charset);
- } catch (UnsupportedEncodingException e) {
- return target;
- }
- }
- /**
- * 初始化设置url传值的参数名
- */
- private void initUrlParameterNames() {
- if (null != urlParamNames) {
- return;
- }
- // 获取所有的url传参的参数名
- urlParamNames = new HashMap<String, String>();
- String st = this.getQueryString();
- if (null == st || 0 == st.length()) {
- return;
- }
- String[] params = this.getQueryString().split("&");
- for (String p : params) {
- if (!p.contains("=")) {
- continue;
- }
- urlParamNames.put(p.substring(0, p.indexOf("=")), null);
- }
- }
- }
- 描述:GetHttpServletRequestWrapper
- <pre>
- HISTORY
- ID DATE PERSON REASON
- 1 2015-3-6 Shixy Create
- 2 2015-3-23 Shixy 增加对post混合传参方式的支持
-
</pre>
-
@author Shixy
-
@since 1.0
*/
public class GetHttpServletRequestWrapper extends HttpServletRequestWrapper {private String charset = "UTF-8";
private static final String ENCODED = "__encoded";
private Map<String, String> urlParamNames = null;
/**
- @param request
*/
public GetHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
initUrlParameterNames();
}
/**
- 获得被装饰对象的引用和采用的字符编码
- @param request
- @param charset
*/
public GetHttpServletRequestWrapper(HttpServletRequest request, String charset) {
super(request);
this.charset = charset;
initUrlParameterNames();
}
@Override
public Enumeration<String> getParameterNames() {
return super.getParameterNames();
}/**
- 实际上就是调用被包装的请求对象的getParameter方法获得参数,然后再进行编码转换
*/
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
// 根据urlParamNames是否包含此值来判断是否需要对其进行get方式转码
if (!urlParamNames.containsKey(name)) {
return value;
}
if (null != value) {
value = convert(value);
}
return value;
}
@Override
public String[] getParameterValues(String name) {
// values也是传值
String[] values = super.getParameterValues(name);
if ((!urlParamNames.containsKey(name))) {
return values;
}
for (int i = 0; i < values.length; i++) {
values[i] = convert(values[i]);
}return values;
}
@Override
public Map<String, String[]> getParameterMap() {
Map<String, String[]> map = super.getParameterMap();
// 是否已经转码的标识位
// 因为map是传引用的,因此多次调用时,原值会被转码转码在转码,因此要设置此标志位,防止多次转码
if ("1".equals(this.getAttribute(ENCODED))) {
return map;
}// 对map中所有的url传参进行编码 // 遍历map中的参数,转换器编码 for (String key : urlParamNames.keySet()) { String[] value = map.get(key); if (value != null) { for (int i = 0; i < value.length; i++) { value[i] = convert(value[i]); } } } this.setAttribute(ENCODED, "1"); return map;
}
/**
- 将字符串转码
- ISO-8859-1为国际通用url编码
- @param target
- @return
*/
private String convert(String target) {
try {
return new String(target.trim().getBytes("ISO-8859-1"), charset);
} catch (UnsupportedEncodingException e) {
return target;
}
}
/**
- 初始化设置url传值的参数名
*/
private void initUrlParameterNames() {
if (null != urlParamNames) {
return;
}
// 获取所有的url传参的参数名
urlParamNames = new HashMap<String, String>();
String st = this.getQueryString();
if (null == st || 0 == st.length()) {
return;
}
String[] params = this.getQueryString().split("&");
for (String p : params) {
if (!p.contains("=")) {
continue;
}
urlParamNames.put(p.substring(0, p.indexOf("=")), null);
}
}
- @param request
}
第二个就是一个简单的filter,用于使用上面的RquestWrapper转码我们的参数:
- /*
- * Copyright (c) 2014, ShiXiaoyong. All rights reserved.
- */
- package com.common.filter;
- import java.io.IOException;
- import javax.servlet.Filter;
- import javax.servlet.FilterChain;
- import javax.servlet.FilterConfig;
- import javax.servlet.ServletException;
- import javax.servlet.ServletRequest;
- import javax.servlet.ServletResponse;
- import javax.servlet.http.HttpServletRequest;
- /**
- * 描述:GetMethodEncodingFilter
- * 针对GET方式提交的表单,进行编码转换
- * <pre>
- * HISTORY
- * ****************************************************************
- * ID DATE PERSON REASON
- * 1 2015-3-6 Shixy Create
- * ****************************************************************
- * </pre>
- *
- * @author Shixy
- * @since 1.0
- */
- public class GetMethodEncodingFilter implements Filter {
- private String charset = "utf-8";
- @Override
- public void destroy() {
- }
- @Override
- public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
- HttpServletRequest req = (HttpServletRequest)request;
- req = new GetHttpServletRequestWrapper(req,charset);
- filterChain.doFilter(req, response);
- }
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
- }
- }
- 描述:GetMethodEncodingFilter
-
针对GET方式提交的表单,进行编码转换
- <pre>
- HISTORY
- ID DATE PERSON REASON
- 1 2015-3-6 Shixy Create
-
</pre>
-
@author Shixy
-
@since 1.0
*/
public class GetMethodEncodingFilter implements Filter {private String charset = "utf-8";
@Override
public void destroy() {
}@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest req = (HttpServletRequest)request; req = new GetHttpServletRequestWrapper(req,charset); filterChain.doFilter(req, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
}
最后把我们的filter配置在web.xml里即可,要注意顺序,最佳位置是setCharatorEncoding那个filter后面。
- <!-- get method url encode -->
- <filter>
- <filter-name>getMethodEncodingFilter</filter-name>
- <filter-class>com.common.filter.GetMethodEncodingFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>encodingFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>