@ResponseBody 总是乱码?这个问题得好好捋一捋!
本文作者:忧郁的马赛克
本文地址:http://1t.click/cDY
关于乱码问题,松哥之前有过一篇文章和大家分享:
在这篇文章的最后,松哥提了一句,“还有一些非常偶尔的情况可能会用到 @RequestMapping 注解中的 produces 属性”,但是对于这个问题并没有展开讲,有的小伙伴可能还是会遇到这个问题,因此今天就来说一说这个话题。
问题背景
本文并不是介绍 @ResponseBody
注解,也不是中文乱码问题的大汇总笔记,这些网上都有很多内容了。这边仅对几年前,一个卡壳了挺久时间的问题的解决过程做一个记录,以警惕自己,达到自醒得目的。
@ResponseBody
注解不用多介绍了,用过 SpringMVC 的同学都很熟了, @ResponseBody
将内容或对象作为 HTTP 响应正文返回,使用 @ResponseBody
将会跳过视图处理部分,而是调用适合的 HttpMessageConverter
,将返回值写入输出流。在日常工作中,通常使用封装好的 ViewModel
进行后台数据的返回,一切正常。但一次在使用 @ResponseBody
进行返回 String
数据的时候,竟会出现中文乱码。
编程的过程免不了遇到各种问题,而遇到问题然后解决问题的这个过程我认为是最让人兴奋的事情。越棘手的问题,解决以后带来的快感也越大(PS:当然解决不了的话,就会越烦躁。。),还是言归正传,谈一下解决错误的过程。
问题分析
最早我一直以为 Spring
配置一下编码过滤器就可以解决任何中文乱码问题,恩,确实一直是这样设置着,然并卵,代码如下
<filter>
<filter-name>characterEncodingFilter</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>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
直接返回 String 会乱码,而返回 ViewAndModel 的那个不会乱码,这是为什么?
其实也可以说是 SpringMVC 的一个 bug ,SpringMVC 有一系列 HttpMessageConverter 去处理用 @ResponseBody
注解的返回值,如返回 VM 则使用 MappingJacksonHttpMessageConverter 或者其他的 HttpMessageConverter ,若返回 String ,则使用 StringHttpMessageConverter ,这个 convert 使用的是字符集是 iso-8859-1 ,而且是 final 的,部分源码如下:
public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");
既然是 String 有问题,那自然就直接从适配器 AnnotationMethodHandlerAdapter 的字符串解析器 StringHttpMessageConverter 入手,设置编码类型即可?NO!
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/plain;charset=UTF-8</value>
</list>
</property>
</bean>
那万金油 response.setContentType("text/html; charset=UTF-8");
呢?NO!
那么重写 StringHttpMessageConverter 应该可以了吗?NO!
上面方法都不行,就尝试着各种百度,说法多种多样,但答案还是:NO!
问题解决
看来我们得从源头开始再理一遍,既然问题在解析器,那么可以从配置文件配置解析器入手。mvc-config.xml 文件从上到下:控制层扫描、国际化配置、文件上传表单解析器、自定义拦截器、视图配置。好像都不是,继续往下,一些 HandlerMapping 和 HandlerAdapter ,还有一句 <mvc:annotation-driven/>
。
网上查阅了一下资料,果然发现问题其实就在这句 <mvc:annotation-driven/>
。
<mvc:annotation-driven/>
是一种简写模式,它会自动注册 DefaultAnnotationHandlerMapping
与 AnnotationMethodHandlerAdapter
两个 bean
,是 SpringMVC
为 @Controller
分发请求所必须的,并且提供了其他一些支持。
上面使用为 StringHttpMessageConverter
设置编码模式其实正常是有效的,但是在使用了 <mvc:annotation-driven/>
语句后,再次显示声明其他 bean
,可能就无效了。
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="UTF-8" />
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
扩展:其他解决方案
除了使用上面那种方案之外,还可以使用下面的:
完全不使用 String 返回,直接都通过统一的 ViewAndModel 去返回,如 ResultModel 等(正常也是这么干的,总还要错误信息等吧);
通过 @RequestMapping 的属性处理,该注解的属性 produces 用于指定返回的内容类型,这算肯定可以了,代码如下:
@RequestMapping(value="/test", method=RequestMethod.POST, produces="text/html;charset=UTF-8")
注意:既然使用了配置 <mvc:annotation-driven>
,还是建议在该配置内部进行处理。
好了,大家有没有 Get 到知识点呢?
●北冥有 Data,其名为鲲,鲲之大,一个 MySQL 放不下!