解決表單提交參數亂碼問題【終極版】不看後悔
提交表單亂碼問題,一直是困然網站開發人員的“吐血”問題,這問題雖說不痛不癢,但是絕對“噁心人”。之前自己遇到這個問題是,一直是能繞過就繞過,懶得理它,直到今天我又遇到它,我知道,我必須得治治它了。
表單提交通常有兩種方式,一種是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,這個類是“主角”,完成對參數的篩選和轉碼:
[java] view plain copy print?
/*
* 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);
}
}
}
第二個就是一個簡單的filter,用於使用上面的RquestWrapper轉碼我們的參數:
[java] view plain copy print?
/*
* 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 {
}
}
最後把我們的filter配置在web.xml裏即可,要注意順序,最佳位置是setCharatorEncoding那個filter後面。
[html] view plain copy print?
<!-- 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>