Spring/Tomcat Rest開發 配置UTF-8編碼

環境/框架:windows7+Intellij Idea+jdk8+tomcat+Spring

起因:寫微信支付接口時遇到過很多編碼錯誤問題,比如:
1. 收到了微信返回、console裏打印出來錯誤描述是“???mchid???appid???”
2. 返回給設備端的出錯描述,postman裏看到是“JSON???????”
3. 另外這裏提到的簽名checksum計算錯誤

目的:使代碼開發、程序運行和讀寫http消息時都使用UTF-8編碼

Disclaimer:只是整理自己遇到且試過的(沒有數據庫、html頁面相關)。解決思路和大部分代碼來源都是StackOverflow(感謝SO答主)。一些能原帖鏈接已經附上、其他用英文google應該都能找到。

Checklist

  1. IDEA settings -> file encodings 都設成UTF-8,目的:將項目源文件都以UTF-8保存。

  2. [optional] 修改IDEA bin/目錄下的idea.exe.vmoptions/idea64.exe.vmoptions文件 或通過Help菜單的Edit Custom VM Options、增加 -Dfile.encoding=UTF8參數,目的:使IDE在UTF-8下運行。
    (仔細想了想其實說不出這個有什麼具體作用…但因爲遇到過console打印亂碼問題,爲了限制/縮小排查範圍就加上了。)
    以上參考intellij-idea-incorrect-encoding-in-console-output

  3. 設置mavengradle編譯選項,具體參考:
    Maven文檔
    how-to-configure-encoding-in-maven
    show-utf-8-text-properly-in-gradle
    目的:保證生成的class文件(特別是其中的static String)都使用utf8編碼。

  4. 配置tomcat。用到的是
    (1)jvm_opts增加 -Dfile.encoding=UTF8 (可以修改catalina.bat或Intellij的run configuration),目的:保證在運行各webapp時用UTF8而不是系統默認編碼(開頭提到的checksum問題)。
    (2)server.xml裏<Connector>增加屬性 URIEncoding="UTF-8",目的:保證GET方法的url(及query string)使用utf8解析。
    兩個非常詳細的參考:
    Tomcat FAQ CharactorEncoding (裏面的How to use UTF-8 everywhere節)
    how-to-get-utf-8-working-in-java-webapps

  5. 配置web.xml裏servlet <filter>,我看到的用Spring的一般都是

    <filter>
        <filter-name>SpringEncodingFilter</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>
    </filter>

    CharacterEncodingFilter的作用Java doc裏寫的很清楚,代碼裏是這樣:

    String encoding = getEncoding(); // 這個是xml裏設置的UTF-8
    if (encoding != null) {
        if (isForceRequestEncoding() || request.getCharacterEncoding() == null) {
            request.setCharacterEncoding(encoding);
        }
        if (isForceResponseEncoding()) {  // isForceRequestEncoding和isForceResponseEncoding都等於forceEncoding
            response.setCharacterEncoding(encoding);
        }
    }

    如果接口開發裏使用的是javax的HttpServletRequestHttpServletResponse,配置完這步後、Content-Type裏的charset應該都是UTF-8了。當然這個只是加個名稱、具體body的stream讀寫還是要自己處理的…
    參考:
    CharacterEncodingFilter
    encoding-and-servlet-api-setcontenttype-or-setcharacterencoding

  6. 然而如果使用Spring web MVC、具體是用到@RequestBody@ResponseBody等annotation時,將stream和目標Object互轉的class是HttpMessageConverter而非CharacterEncodingFilter
    參考 RequestMapping Java doc
    其中提到比如:

    @ResponseBody annotated methods (Servlet-only) for access to the Servlet response HTTP contents. The return value will be converted to the response stream using message converters.

    還有這個09年的帖子

    CharacterEncodingFilter can’t help here, because String returned by @RequestBody-annotated method is encoded by StringHttpMessageConverter

    (沒有在Spring代碼裏找到到底是哪邊決定使用CharacterEncodingFilter或者HttpMessageConverter,但既然官方doc裏這麼說了、應用開發知道就好了吧…)

    此時如果Controller方法裏注入或返回的參數是String類型1、且沒有指定編碼(@RequestMapping裏沒有produces),仍然是有默認charset=ISO-8859-1會被框架自動添加上去。這個默認charset來自的是StringHttpMessageConverter

    public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");

    參考
    StringHttpMessageConverter Java doc
    who-sets-response-content-type-in-spring-mvc-responsebody (具體幾種配置方法可參考這個鏈接)

    我希望統統都用utf8,所以直接register-defaults="false"並改了defaultCharset,寫出來的xml是這樣:

    <mvc:annotation-driven>
        <mvc:message-converters register-defaults="false">
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <constructor-arg>
                    <util:constant static-field="java.nio.charset.StandardCharsets.UTF_8"/>
                </constructor-arg>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

    然而09年那個帖子的回答裏寫的直接是

    <constructor-arg value="UTF-8"> 

    實際測下來是可以用的(注入了UTF-8 Charset對象),不是很確定Spring到底是怎麼解析的…

  7. 4主要處理GET Request、5主要處理POST Request、6主要處理Response,這樣server端應該就沒問題了。而Spring的RestTemplate client,參考how-can-i-tell-resttemplate-to-post-with-utf-8-encoding,也是一樣加了utf-8的message converter。

  8. 好奇了下Jackson,結果說Jackson因爲Json關係只支持的UTF-8、UTF-16和UTF-32、且可以自動檢測,參考
    jackson-objectmapper-with-utf-8-encoding
    JsonEncoding.java
    jackson-core/issues/222
    特地查了Json的encoding要求,還確實是這樣。
    xml的話,開頭就是 <?xml version="1.0" encoding="UTF-8"?>

  9. 話說回來,編碼encoding、字符集charset到底是什麼?UTF-8、UTF-16、UTF-32又有什麼區別?
    又翻回到以前看的…
    whats-the-difference-between-encoding-and-charset
    what-is-unicode-utf-8-utf-16
    評論提到的入門必讀absolute-minimum-must-know
    甚至這個貼吧帖子: 我早就想問了,極影下載的GB和BIG5,究竟是什麼區別?
    不過GB是編碼,GB2312和GBK是字符集;而Unicode是字符集,UTF-8、UTF-16、UTF-32都是編碼;開始確實有點繞…


  1. 昨天提交完文檔就遇到同組裏其他人開發的接口裏fastJson的JSONObject注入出錯、後來參考了這個17年4月7號的教程、加了FastJsonHttpMessageConverter就好。總之Spring預設就掛載這多個MessageConverter(前文鏈接裏2.2. The Default Message Converters列表)、有多個converter可以對supportedMediaTypes property進行細化配置,暫時沒有更深入瞭解了就不寫了
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章