Spring MVC返回json乱码

前言

json又双叒叕返回乱码了!
乱码一般都是编码问题,比如一个字符串你好世界, 用GBK编码后, 再用UTF-8解码, 就会出现乱码问题。

样例

1
2
3
4
5
6
7
8
@Controller
public class TestController{
@GetMapping("/test")
@ResponseBody
public String test() {
return "你好世界";
}
}

使用CharacterEncodingFilter过滤器(没用)

在 web.xml 中加入CharacterEncodingFilter过滤器, 对request和response进行编码转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<filter>
<filter-name>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>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>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

源码很简单, 就是调用setCharacterEncoding方法设置编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class CharacterEncodingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

String encoding = getEncoding();
if (encoding != null) {
if (isForceRequestEncoding() || request.getCharacterEncoding() == null) {
request.setCharacterEncoding(encoding);
}
if (isForceResponseEncoding()) {
response.setCharacterEncoding(encoding);
}
}
filterChain.doFilter(request, response);
}
}

CV大法重写StringHttpMessageConverter类

行吧, 自己解决不了, 上stackoverflow看看,
在使用<mvc:annotation-driven />自动驱动的前提下,
发现@ResponseBody返回值是String类型的话。
会调用StringHttpMessageConverter这个类进行转换。

1
2
3
4
5
public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {
public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");
private volatile List<Charset> availableCharsets;
private boolean writeAcceptCharset = true;
}

默认是ISO-8859-1编码, 而且是final修饰的。这就意味这不能继承这个方法了。
那就用CV大法吧。

1
2
3
4
5
public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private volatile List<Charset> availableCharsets;
private boolean writeAcceptCharset = true;
}

并且在<mvc:annotation-driven />内注入这个Bean

1
2
3
4
5
<mvc:annotation-driven>  
<mvc:message-converters register-defaults="true">
<bean class="com.chuanliu.platform.activity.basic.converter.MyStringHttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>

使用produces属性完成

使用CV大法是很不好的习惯,
解决spring-mvc @responseBody注解返回json 乱码问题】这篇文章提出可以使用produces属性完成

1
2
3
4
5
6
7
8
@Controller
public class TestController{
@GetMapping(value = "/test", produces="text/html;charset=UTF-8")
@ResponseBody
public String test() {
return "你好世界";
}
}

省了一大堆配置, 但还是要在每个@ResponseBody方法使用CV大法, 写入produces="text/html;charset=UTF-8"

直接返回对象

(二)Java 中文乱码学习 与Spring @ResponseBody中的乱码 - Spring @ResponseBody中的乱码】中提到
在使用<mvc:annotation-driven />自动驱动的前提下,
如果直接返回String类型, 则会调用StringHttpMessageConverter
如果直接返回对象类型, 则会调用MappingJackson2HttpMessageConverter

MappingJackson2HttpMessageConverter的父类AbstractJackson2HttpMessageConverter中。
可以看到使用了UTF-8编码。

1
2
3
public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
}

参考资料