zoukankan      html  css  js  c++  java
  • 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代码如下: 
    Java代码  收藏代码
    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配置文件里,默认如下: 
    Xml代码  收藏代码
    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 
    Java代码  收藏代码
    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代码  收藏代码
    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一把我的页面: 

     
  • 相关阅读:
    HDU 4348 To the moon(可持久化线段树)
    HDU 5875 Function 大连网络赛 线段树
    HDU 5877 2016大连网络赛 Weak Pair(树状数组,线段树,动态开点,启发式合并,可持久化线段树)
    HDU 5876 大连网络赛 Sparse Graph
    HDU 5701 中位数计数 百度之星初赛
    CodeForces 708B Recover the String
    Java实现 蓝桥杯 算法提高 套正方形(暴力)
    ASP.NET生成验证码
    ASP.NET生成验证码
    ASP.NET生成验证码
  • 原文地址:https://www.cnblogs.com/chenying99/p/2453017.html
Copyright © 2011-2022 走看看