如果在Spring的@Responsbody返回的内容中,发现乱码,需要从以下几方面来解决。
1. 确保在web.xml中配置Spring的Character Encoding Filter:
- <!-- Servlet Encoding Start -->
- <filter>
- <filter-name>Set Character Encoding</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>
- <filter-mapping>
- <filter-name>Set Character Encoding</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
- <!-- Servlet Encoding End -->
2. 如果在@ResponseBody注释的方法下的返回值类型是String,则在结果返回给用户之前,如果在Spring的配置文件中配置了<mvc:annotation-driven />,则Spring默认会去调用一个叫StringHttpMessageConverter
的类进行内容的输出。为什么会出现乱码呢?
解决方法1: 查看StringHttpMessageConverter的源码,发下StringHttpMessageConverter类中的默认编码是ISO-8859-1编码,改编码是西欧字符集编码,显然不会支持中文。(PS:感觉这是Spring的bug,明显应该用UTF-8嘛,我已经在Spring官网上中报了这个bug)
- public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");
- private final Charset defaultCharset;
- private final List<Charset> availableCharsets;
而且从上面的代码中可以看出,与字符相关的3个变量都是final的,意味着我们不能通过set或者构造器的注入去动态的更改上面3个值。
进一步分析源码可以看出,StringHttpMessageConverter继承与AbstractHttpMessageConverter<String>,分析该抽象类得,相关的操作字符编码的方法可以重写,于是我们可以自定义一个类继承StringHttpMessageConverter,然后重写相关的方法,代码如下:
- /**
- *
- */
- package com.chuanliu.platform.activity.basic.converter;
- import java.io.IOException;
- import java.nio.charset.Charset;
- import java.util.Arrays;
- import java.util.List;
- import org.springframework.http.HttpOutputMessage;
- import org.springframework.http.MediaType;
- import org.springframework.http.converter.StringHttpMessageConverter;
- import org.springframework.util.StreamUtils;
- /**
- * 用于处理中文乱码问题: Spring bug -
- *
- * In StringHttpMessageConverter, the default char set is ISO-8859-1(西欧字符集)
- *
- * @author Josh Wang(Sheng)
- *
- * @email josh_wang23@hotmail.com
- */
- public class UTF8StringHttpMessageConverter extends StringHttpMessageConverter {
- private static final MediaType UTF8 = new MediaType("text", "plain",
- Charset.forName("UTF-8"));
- private boolean writeAcceptCharset = true;
- @Override
- protected void writeInternal(String s, HttpOutputMessage outputMessage)
- throws IOException {
- if (this.writeAcceptCharset)
- outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());
- Charset charset = UTF8.getCharSet();
- StreamUtils.copy(s, charset, outputMessage.getBody());
- }
- @Override
- protected List<Charset> getAcceptedCharsets() {
- return Arrays.asList(UTF8.getCharSet());
- }
- @Override
- protected MediaType getDefaultContentType(String t) throws IOException {
- return UTF8;
- }
- public boolean isWriteAcceptCharset() {
- return writeAcceptCharset;
- }
- public void setWriteAcceptCharset(boolean writeAcceptCharset) {
- this.writeAcceptCharset = writeAcceptCharset;
- }
- }
定义好上面的类后,只需要将该类注册到Spring的annotaion 处理序列中即可,于是当@ResponseBody中返回的类型是String类型时,Spring将会调用上面自定义类中复写的方法,从而返回UTF-8的编码:
- <mvc:annotation-driven>
- <mvc:message-converters register-defaults="true">
- <bean class="com.chuanliu.platform.activity.basic.converter.UTF8StringHttpMessageConverter"/>
- </mvc:message-converters>
- </mvc:annotation-driven>
解决方法2:最简单的方法:
在@Responsebody标注的方法上加上:produces="application/json;charset=utf-8"
如:
- @RequestMapping(value="/circle/{cid}", produces="application/json;charset=utf-8")
- @ResponseBody
解决方法3:指定返回值为对象不要返回String
既然上面分析了其原因是上面的StringHttpMessageConverter类中使用了ISO-8859-1编码,那么如果业务逻辑允许,我们完全可以不要返回一个字符串(String),完全可以返回一个对象,这样Spring默认会去调用一个叫MappingJackson2HttpMessageConverter的类,该类不仅会将你返回的对象转换成JSON返回,而且该类中使用的是我们想要的UTF-8字符集,相关代码为:
- public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
这种方法其实也是种很好的方法,因为更多的时候我们希望我们返回的对象直接转换成一个json字符串返回。如果你想判断Spring中最终有没有调用:MappingJackson2HttpMessageConverter或者是StringHttpMessageConverter或者是自定义的UTF8StringHttpMessageConverter的方法,你可以在这两个类的源码的writeInternal()等方法中设置断点。如果没有按期望的去调用MappingJackson2HttpMessageConverter中的writeInternal()等方法,则可能你需要配置让Spring对默认返回的视图按Json来处理,在我的应用中,我只只需要在如下的内容协商器中设置json viewer即可:
- <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
- <!-- 设置为true以忽略对Accept Header的支持 -->
- <property name="ignoreAcceptHeader" value="true" />
- <!-- 在没有扩展名时即: "/user/1" 时的默认展现形式 -->
- <property name="defaultContentType" value="text/html" />
- <!-- 扩展名至mimeType的映射,即 /user.json => application/json -->
- <property name="mediaTypes">
- <map>
- <entry key="html" value="text/html"/>
- <entry key="json" value="application/json" />
- <entry key="xml" value="application/xml" />
- </map>
- </property>
- <!-- 用于开启 /userinfo/123?format=json 的支持,false为关闭之,其实.json的方式更简洁 -->
- <property name="favorParameter" value="false" />
- <property name="viewResolvers">
- <list>
- <bean class="org.springframework.web.servlet.view.BeanNameViewResolver" />
- <bean
- class="org.springframework.web.servlet.view.InternalResourceViewResolver">
- <property name="prefix" value="/WEB-INF/jsp/"></property>
- <property name="suffix" value=".jsp"></property>
- </bean>
- </list>
- </property>
- <<strong>property name="defaultViews">
- <list>
- <!-- for application/json -->
- <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
- </list></strong>
- </property>
- </bean>
- <!-- Default view resolver will be used if the upper resolve unavailable(the default format is html) -->
- <bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:order="3">
- <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
- <property name="contentType" value="text/html"/>
- <property name="prefix" value="/WEB-INF/jsp/"/>
- <property name="suffix" value=".jsp"/>
- </bean>
- <!-- json view -->
- <!-- <bean id="defaultJsonView" class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"/> -->