SpringMVC使用@ResponseBody輸出字符串時遇到的亂碼問題及解決辦法

今番又遇到亂碼問題,有時候真以爲英語母語的那些地區確實挺省事的,至少不用爲了這個經典麻煩去折騰。 

網絡上討論亂碼問題的文章不少,由於各做者使用的計算機環境的不一樣,每每不是很全面。 
這裏很是推薦的一篇文章: 
http://dohkoos.name/java-garbled-analysis.html 

簡而言之,亂碼的「根本緣由是因爲編碼和解碼採用的不是同一種碼」。例如做者所舉的例子,使用GBK編碼爲UTF-8,使用ISO-8859從UTF-8解碼,可能會致使亂碼問題。這就比如有一篇中文文章想給王五看,不過這篇文章先由張三翻譯成爲了英文,而後再由李四翻譯成俄文(而不是翻譯回中文),可是王五隻看得懂中文,因而就麻瓜了。 

咱們須要保持編碼或者解碼兩頭,所使用的字符集轉換方向須要正好相反:使用 GBK --> UTF-8 與 UTF-8 --> GBK。因爲Java採用了UTF-8編碼,因此編碼解碼均以UTF-8爲中介。 
對於翻譯而言,就是先至關於: 先 中譯英,對應的解碼,反過來就是 英譯中。 

遇到亂碼問題,一般的檢查項包括: 
1. 編輯器保存文件的字符集; 
2. 數據庫的字符集; 
3. 應用服務器或者Web服務器處理字符串採用的字符集 
4. JSP對於字符集聲明 
5. Servlet過濾器,以及MVC框架攔截器對於字符集的處理 
6. 其它涉及字符集處理的環節 

檢查各個環節,統一按UTF-8設置。推斷我此次碰到的問題屬於上述第6中狀況。 

由於是經過SpringMVC提供的註解@ResponseBody來返回一個JSON字符串,而後在客戶端上解析JSON(現現在以JSON做爲數據交換格式貌似愈來愈時髦了,客戶端我用的比較多的是jqGrid或者ExtJS)。 

Controller代碼以下: javascript

Java代碼 php

 收藏代碼

  1. @Controller  
  2. @RequestMapping("/*")  
  3. public class HelloController {  
  4.     private transient final Log log = LogFactory.getLog(HelloController.class);  
  5.       
  6.     @Autowired  
  7.     private UserManager mgr = null;  
  8.       
  9.     @RequestMapping(value="hello_list.do", method = RequestMethod.POST)  
  10.     @ResponseBody  
  11.     public String helloList() {  
  12.         StringBuilder str = new StringBuilder("{totalProperty:100,root:[");  
  13.           
  14.         List<User> users = mgr.getUsers();  
  15.         for (User user : users) {  
  16.             str.append("{id: ").append(user.getId());  
  17.             str.append(", name:'").append(user.getLastName());  
  18.             str.append("', descn:'").append(user.getFullName()).append("'},");  
  19.         }  
  20.         str.append("{id:4, name:'생활', descn:'Китай'},");  
  21.         str.append("{id:5, name:'tchen8', descn:'中文'}]}");  
  22.           
  23.         log.info(str.toString());  
  24.           
  25.         return str.toString();  
  26.     }  
  27.   
  28. }  



在Spring配置文件裏,默認以下: html

Xml代碼 java

 收藏代碼

  1. <!-- Enables the Spring MVC @Controller programming model -->  
  2. <mvc:annotation-driven />  



調試程序,控制檯輸出日誌看到是中文,可是在firebug中看到的服務器端送過來的字符串是???? (若是是 "口口口"這樣的輸出,須要先排除是否爲系統的字體缺失),因而判斷是服務器最後往端口寫字符串流的時侯字符集不對。 

經過調試跟蹤Spring的源碼,聲明@ResponseBody時,Spring會經過AnnotationMethodHandlerAdapter去尋找對應的HttpMessageConverter, 咱們這裏聲明返回的類型是String,因而對應StringHttpMessageConverter。經過實驗,猜想這個StringHttpMessageConverter也就是<mvc:annotation-driven />觸發的默認的字符串轉換工做類。 

比較不幸的是,StringHttpMessageConverter所使用的默認字符集是ISO-8859-1 web

Java代碼 spring

 收藏代碼

  1. ......  
  2. public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {  
  3.   
  4.     public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");  
  5. ......  



這裏不得不提的是與StringHttpMessageConverter 同級的類MappingJacksonHttpMessageConverter,天知道是什麼緣由:同一個做者,對於這兩個類,默認字符集一個是ISO-8859-1,一個是UTF-8。 

既然事已如此,那就想辦法把這個地方用到的ISO-8859-1也改爲UTF-8了。有兩個思路: 
1. 替換默認字符集; 
2. 替換StringHttpMessageConverter 

搜索了一下,先看到這個解決辦法: 
http://forum.springsource.org/showthread.php?t=81858 
這裏提供的是使用一個所謂的ConfigurableStringHttpMessageConverter來替代StringHttpMessageConverter,基本的思路技術是:因爲StringHttpMessageConverter中的默認字符集變量聲明爲final,沒法直接經過繼承去覆蓋,那就把StringHttpMessageConverter照抄一遍,構造函數中新增一個表明字符集的輸入參數,而後在配置文件裏面經過構造方法注入UTF-8。在配置文件中,將這個Bean聲明在<mvc:annotation-driven />前面,從而可以先於StringHttpMessageConverter被Spring識別和注入。 

可是這個方法多少有些蠻幹的味道,基於它簡化的一個版本能夠以下,即經過繼承StringHttpMessageConverter,而後在子類中注入咱們想要的字符集配置: 數據庫

Java代碼 服務器

 收藏代碼

  1. public class MyStringHttpMessageConverter extends StringHttpMessageConverter {  
  2.   
  3.     public MyStringHttpMessageConverter(Charset defaultCharset) {  
  4.         List<MediaType> mediaTypeList = new ArrayList<MediaType>();  
  5.         mediaTypeList.add(new MediaType("text", "plain", defaultCharset));  
  6.         mediaTypeList.add(MediaType.ALL);  
  7.         super.setSupportedMediaTypes(mediaTypeList);  
  8.     }  
  9.       
  10. }  



Bean的配置依然相似: 網絡

Xml代碼 mvc

 收藏代碼

  1. ...  
  2. ...  
  3.     <beans:bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">  
  4.         <beans:property name="messageConverters">  
  5.             <util:list>  
  6.                 <beans:bean id="stringHttpMessageConverter" class="org.tchen8.myapp.common.ConfigurableStringHttpMessageConverter">  
  7.                     <beans:constructor-arg value="UTF-8" />  
  8.                 </beans:bean>  
  9.             </util:list>  
  10.         </beans:property>  
  11.     </beans:bean>  
  12.   
  13.     <!-- Enables the Spring MVC @Controller programming model -->  
  14.     <mvc:annotation-driven />  
  15. ...  
  16. ...  



上面的辦法是以屬性注入的方式,替換了默認的字符集,但爲此也須要把converter替換。 



另一個比較簡潔的辦法,則不須要本身寫converter類,而是直接經過屬性注入,修改StringHttpMessageConverter的默認配置。 

Xml代碼 

 收藏代碼

  1. ...  
  2.     <beans:bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">    
  3.         <beans:property name="messageConverters">    
  4.             <util:list>    
  5.                 <beans:bean class="org.springframework.http.converter.StringHttpMessageConverter">    
  6.                     <beans:property name="supportedMediaTypes">    
  7.                         <util:list>  
  8.                             <beans:value>text/html;charset=UTF-8</beans:value>  
  9.                         </util:list>    
  10.                     </beans:property>    
  11.                 </beans:bean>    
  12.             </util:list>    
  13.         </beans:property>    
  14.     </beans:bean>  
  15. ...  


上面的這個辦法,實際上經過setSupportedMediaTypes方法,其實也就是StringHttpMessageConverter在類註釋中所提到的辦法: 



若是再多看一下StringHttpMessageConverter的源碼,能夠到它的父類中AbstractHttpMessageConverter有這麼個方法: 

Java代碼 

 收藏代碼

  1. ...  
  2.     /** 
  3.      * Returns the default content type for the given type. Called when {@link #write} 
  4.      * is invoked without a specified content type parameter. 
  5.      * <p>By default, this returns the first element of the 
  6.      * {@link #setSupportedMediaTypes(List) supportedMediaTypes} property, if any. 
  7.      * Can be overridden in subclasses. 
  8.      * @param t the type to return the content type for 
  9.      * @return the content type, or <code>null</code> if not known 
  10.      */  
  11.     protected MediaType getDefaultContentType(T t) {  
  12.         List<MediaType> mediaTypes = getSupportedMediaTypes();  
  13.         return (!mediaTypes.isEmpty() ? mediaTypes.get(0) : null);  
  14.     }  
  15. ...  



註釋中寫的明白:"Can be overridden in subclasses." 那就沒必要客氣了。因而咱們大概也能有以下的作法: 

Java代碼 

 收藏代碼

  1. ...  
  2. public class MyStringHttpMessageConverter2 extends StringHttpMessageConverter {  
  3.       
  4.     private static final MediaType utf8 = new MediaType("text", "plain", Charset.forName("UTF-8"));   
  5.   
  6.     @Override  
  7.     protected MediaType getDefaultContentType(String dumy) {  
  8.         return utf8;  
  9.     }  
  10.       
  11. }  
  12. ...  


對應的配置: 

Xml代碼 

 收藏代碼

  1. <beans:bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">  
  2.     <beans:property name="messageConverters">  
  3.         <util:list>  
  4.             <beans:bean id="myStringHttpMessageConverter2" class="org.tchen8.myapp.common.MyStringHttpMessageConverter2" />  
  5.         </util:list>  
  6.     </beans:property>  
  7. </beans:bean>   
  8.   
  9. <!-- Enables the Spring MVC @Controller programming model -->  
  10. <mvc:annotation-driven />  



以上的幾個方法,都能解決@ResponseBody致使的亂碼問題,雖然StringHttpMessageConverter未來確實有可能把默認字符集修改爲UTF-8,從而致使上述功夫最後變成白忙活。但也確實感謝有這麼個小阻礙,迫使本身去分析問題尋找答案。收穫不在於結果,而在過程吧 

最後show一把個人頁面: 

 

相關文章
相關標籤/搜索