Spring MVC的Post請求參數(shù)中文亂碼的原因&處理

一俊犯、項目配置

  1. Spring 4.4.1-RELEASE
  2. Jetty 9.3.5
  3. JDK 1.8
  4. Servlet 3.1.0
  5. web.xml文件中沒有配置編解碼Filter

二、實際遇到的問題:
客戶端(比如java)發(fā)送post請求訪問接口伤哺,數(shù)據(jù)放在body里面燕侠,每個參數(shù)utf-8編碼。
從body里面取出的中文參數(shù)是亂碼立莉。


下面是發(fā)送請求的代碼和服務端接收請求的代碼绢彤。

  • 客戶端代碼。
    這是一個真實的第三方訪問API的案例蜓耻,這段代碼請求到PHP系統(tǒng)正常茫舶,請求到java系統(tǒng)就會出現(xiàn)亂碼。
    但是中文參數(shù)放到URL中解碼正常刹淌,放到請求體中就是亂碼饶氏。
    通過httpclient4.1發(fā)送Post請求如下:
    public static void postData(String sign, String timestamp) { // 創(chuàng)建默認的httpClient實例.
    CloseableHttpClient httpclient=null;
    String result="";
    try {
    httpclient = HttpClients.createDefault();
    String url = "http://example/api/entry";
    HttpPost httpPost = new HttpPost(url); //設置請求和傳輸超時時間
    RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(6000).setConnectTimeout(6000).build();
    httpPost.setConfig(requestConfig);
    MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);
    entity.addPart("app_id", new StringBody("c5eb3ba8c0e7326559", Charset.forName("utf-8")));
    entity.addPart("method", new StringBody("kdt.item.add", Charset.forName("utf-8")));
    entity.addPart("timestamp", new StringBody(timestamp));
    entity.addPart("format", new StringBody("json", Charset.forName("utf-8")));
    entity.addPart("v", new StringBody("1.0", Charset.forName("utf-8")));
    entity.addPart("sign", new StringBody(sign, Charset.forName("utf-8")));
    entity.addPart("sign_method", new StringBody("md5", Charset.forName("utf-8")));
    entity.addPart("cid", new StringBody("5000000", Charset.forName("utf-8")));
    entity.addPart("tag_ids", new StringBody("0", Charset.forName("utf-8")));
    entity.addPart("price", new StringBody("0.01", Charset.forName("utf-8")));
    entity.addPart("title", new StringBody("測試", Charset.forName("utf-8")));
    entity.addPart("desc", new StringBody("test1", Charset.forName("utf-8"))); //是否是虛擬商品。0為否芦鳍,1為是嚷往。目前不支持虛擬商品
    entity.addPart("is_virtual", new StringBody("0", Charset.forName("utf-8")));
    entity.addPart("post_fee", new StringBody("0.0", Charset.forName("utf-8"))); //Sku的屬性串。格式:pText:vText;pText:vText柠衅,多個sku之間用逗號分隔皮仁,如:顏色:黃色;尺寸:M,顏色:黃色;尺寸:S。pText和vText文本中不可以存在冒號和分號以及逗號
    entity.addPart("sku_properties", new StringBody("color:white", Charset.forName("utf-8")));
    entity.addPart("sku_quantities", new StringBody("998,999", Charset.forName("utf-8")));
    entity.addPart("sku_prices", new StringBody("0.01,0.02", Charset.forName("utf-8")));
    entity.addPart("sku_outer_ids", new StringBody("null,null", Charset.forName("utf-8"))); //該商品的外部購買地址。當用戶購買環(huán)境不支持微信或微博支付時會跳轉(zhuǎn)到此地址
    entity.addPart("buy_url", new StringBody("http://img.cdn.sb.hongware.com/1461836641703511.gif", Charset.forName("utf-8")));
    entity.addPart("quantity", new StringBody("1998", Charset.forName("utf-8"))); //寶貝修改的時候需要這個參數(shù)
    httpPost.setEntity(entity);
    CloseableHttpResponse response = httpclient.execute(httpPost);
    try {
    HttpEntity httpEntity = response.getEntity();
    System.out.println(httpEntity.getContent());
    InputStream content = httpEntity.getContent();
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(content));
    String line;
    while ( (line=bufferedReader.readLine()) != null) {
    System.out.println(line);
    }
    } catch (Exception e) {
    e.printStackTrace();
    } finally {
    response.close();
    }
    } catch (Exception e) {
    e.printStackTrace();
    } finally {
    try {
    httpclient.close();
    } catch (Exception e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }
    }

  • 服務端代碼如下
    為了簡化演示贷祈,我把參數(shù)提取代碼Map<String, String[]> parameterMap = request.getParameterMap()冗余在這個入口函數(shù)中趋急,以便說明問題:
    @RequestMapping(value = "/api/entry", produces = "application/json;charset=utf-8")
    public DeferredResult<Object> sign(HttpServletRequest request,HttpServletResponse response) {
    DeferredResult<Object> deferredResult = new DeferredResult<>();
    Map<String, String[]> parameterMap = request.getParameterMap();
    String method = request.getParameter("method");
    if (StringUtils.isEmpty(method)) {
    ResponseEntity<String> responseEntity = new ResponseEntity<String>( String.format(Constants.ERROR_RESPONSE, 50000, "service or method is null"), HttpStatus.valueOf(200));
    deferredResult.setResult(responseEntity);
    return deferredResult;
    }
    int lastIndex = method.lastIndexOf(".");
    String service = method.substring(0, lastIndex);
    method = method.substring(lastIndex + 1);
    event.setService(service);
    event.setMethod(method);
    event.setResult(deferredResult);
    proxy.doAction(request,response,event);
    return deferredResult;
    }

服務端通過Map<String, String[]> parameterMap = request.getParameterMap()取出所有參數(shù),傳進來title參數(shù)是亂碼J铺堋呜达!

三、根本原因
Servlet 3.0規(guī)范中有關(guān)請求數(shù)據(jù)編碼的解釋如下:

當前很多瀏覽器并不發(fā)送帶Content-Type頭部的字符編碼標識符粟耻,它會把字符編碼的決定留在讀取HTTP請求的時候查近。如果客戶端沒有指明編碼,容器用來創(chuàng)建請求讀和解析POST數(shù)據(jù)的默認編碼必須是"ISO-8859-1"挤忙。然而霜威,為了提示開發(fā)者客戶端沒有成功發(fā)送一個字符編碼,容器中g(shù)etCharacterEncoding方法會返回null册烈。
如果客戶端沒有設置字符編碼戈泼,并且請求數(shù)據(jù)使用了不同編碼而不是上述的默認編碼,程序?qū)霈F(xiàn)中斷赏僧。為了糾正這種狀態(tài)大猛,一個新的方法setCharacterEncoding(String enc) 被添加到ServletRequest接口。開發(fā)者調(diào)用這個方法能重寫容器提供的字符編碼淀零。這個方法必須在解析request中任何post數(shù)據(jù)或者讀任何輸入之前調(diào)用挽绩。一旦數(shù)據(jù)已經(jīng)被讀取,調(diào)用這個方法不會影響它的編碼驾中。

另外一種相同的解釋:

網(wǎng)絡上同樣含義的解釋

四琼牧、3種解決方法

  1. 在web.xml中配置編解碼Filter
    <filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
    <param-name>encoding</param-name>
    <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
    <param-name>forceEncoding</param-name>
    <param-value>true</param-value>
    </init-param>
    <async-supported>true</async-supported>
    </filter>
    <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    關(guān)于這段配置需要強調(diào)兩點:
  • web.xml中,這段配置要放在所有filter的最前面哀卫,否則會不生效巨坊,根本原因請見上述第三點的解釋。
  • 兩個初始化參數(shù)的作用此改,其實看這個Filter的源碼就一目了然趾撵,這兩個參數(shù)是用來決定是否要設置request和response中的編碼。源碼很簡潔:
    public class CharacterEncodingFilter extends OncePerRequestFilter {
    private String encoding;
    private boolean forceEncoding = false;
    public CharacterEncodingFilter() { }
    public void setEncoding(String encoding) {
    this.encoding = encoding;
    }
    public void setForceEncoding(boolean forceEncoding) {
    this.forceEncoding = forceEncoding;
    }
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    if(this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) {
    request.setCharacterEncoding(this.encoding);
    if(this.forceEncoding) {
    response.setCharacterEncoding(this.encoding);
    }
    }
    filterChain.doFilter(request, response);
    }
    }
  1. 設置Content-Type
    如果post請求方式是x-www-form-urlencoded共啃,那么設置如下:
    Content-Type=application/x-www-form-urlencoded;charset=utf-8
    這樣通過request對象取body體里面的中文是正常的占调。
    這種方式有一點需要注意: 如果請求方式是multipart/form-data,如上設置會導致request取不到參數(shù)移剪。Content-Type要與傳遞數(shù)據(jù)匹配(本文data)
  2. 手動編解碼
    比如參數(shù)title="測試"究珊,這樣取出來就是"測試"。
    String str = new String(request.getParameter("title").getBytes("iso-8859-1"), "utf-8");

綜上所有纵苛,最優(yōu)雅的方式是第一種解決方案--通過框架的Filter去處理剿涮。
你僅專注于業(yè)務代碼就好言津。

參考資料

  1. ajax post data獲取不到數(shù)據(jù)
  2. Servlet 3.0規(guī)范
  3. HTTP Content-Type常用對照表
  4. Spring官網(wǎng)--Consumable Media Types章節(jié)
  5. ISO-8859-1
  6. ISO-8859-1為何能顯示中文
  7. 字符編碼
  8. Media Type
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市取试,隨后出現(xiàn)的幾起案子悬槽,更是在濱河造成了極大的恐慌,老刑警劉巖瞬浓,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件初婆,死亡現(xiàn)場離奇詭異,居然都是意外死亡猿棉,警方通過查閱死者的電腦和手機磅叛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來萨赁,“玉大人宪躯,你說我怎么就攤上這事∥挥兀” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵详瑞,是天一觀的道長掂林。 經(jīng)常有香客問我,道長坝橡,這世上最難降的妖魔是什么泻帮? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮计寇,結(jié)果婚禮上锣杂,老公的妹妹穿的比我還像新娘。我一直安慰自己番宁,他們只是感情好元莫,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蝶押,像睡著了一般踱蠢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上棋电,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天茎截,我揣著相機與錄音,去河邊找鬼赶盔。 笑死企锌,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的于未。 我是一名探鬼主播撕攒,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼陡鹃,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了打却?” 一聲冷哼從身側(cè)響起杉适,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎柳击,沒想到半個月后猿推,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡捌肴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年蹬叭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片状知。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡秽五,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出饥悴,到底是詐尸還是另有隱情坦喘,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布西设,位于F島的核電站瓣铣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏贷揽。R本人自食惡果不足惜棠笑,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望禽绪。 院中可真熱鬧蓖救,春花似錦、人聲如沸印屁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雄人。三九已至巨柒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間柠衍,已是汗流浹背洋满。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留珍坊,地道東北人牺勾。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像阵漏,于是被迫代替她去往敵國和親驻民。 傳聞我的和親對象是個殘疾皇子翻具,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)回还,斷路器裆泳,智...
    卡卡羅2017閱讀 134,628評論 18 139
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關(guān)的語法柠硕,內(nèi)部類的語法工禾,繼承相關(guān)的語法,異常的語法蝗柔,線程的語...
    子非魚_t_閱讀 31,598評論 18 399
  • 編碼問題一直困擾著開發(fā)人員闻葵,尤其在 Java 中更加明顯,因為 Java 是跨平臺語言癣丧,不同平臺之間編碼之間的切換...
    x360閱讀 2,468評論 1 20
  • 本文包括:1槽畔、Filter簡介2、Filter是如何實現(xiàn)攔截的胁编?3厢钧、Filter開發(fā)入門4、Filter的生命周期...
    廖少少閱讀 7,256評論 3 56
  • 一套完整的登陸注冊業(yè)務邏輯 準備部分基礎工具類Basepackage com.jericho.tools;impo...
    JerichoPH閱讀 2,438評論 0 9