SpringMVC响应数据中文乱码解决方案详解
一、中文乱码起源?
先看看乱码效果图,如下图所示:
上图中“???”就是中文不能正常显示,出现乱码了。为什么会导致中文乱码?
打开浏览器的开发者工具,按F12也可以打开;查看response header(响应头) 的Content-Type属性值,如下图所示:
原来在springMVC中Content-Type编码值默认是ISO-8859-1,而浏览器一般以UTF-8编码渲染页面的。所以中文乱码就发生了。
二、怎样解决中文编码?
这里就提出几种方案,包括最常见的错误方案。
1. (错误方案)在Controller中修改响应头Content-Type的值为"text/html;charset=utf-8"。如下图所示:
修改之后,乱码问题还是没有解决;主要是因为SpringMVC内部会调用MessageConverter转换器将你设置的编码覆盖成默认的编码。覆盖大致流程:当在controller中使用return时,会触发returnValueHandlers方法,源码如下:
/**
*类RequestMappingHandlerAdapter中295-303行
**/
public void setReturnValueHandlers(@Nullable List<HandlerMethodReturnValueHandler> returnValueHandlers) {
if (returnValueHandlers == null) {
this.returnValueHandlers = null;
}
else {
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite();
this.returnValueHandlers.addHandlers(returnValueHandlers);
}
}
以上源码可知,当return非null是会触发HandlerMethodReturnValueHandlerComposite类的addHandlers。在这就不深入其他类了,继续分析类RequestMappingHandlerAdapter中的构造函数。源码如下:
/**
*类RequestMappingHandlerAdapter中195-209行
**/
public RequestMappingHandlerAdapter() {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
this.messageConverters = new ArrayList<>(4);
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(stringHttpMessageConverter);
try {
this.messageConverters.add(new SourceHttpMessageConverter<>());
}
catch (Error err) {
// Ignore when no TransformerFactory implementation is available
}
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
}
以上代码可以看到初始化了类StringHttpMessageConverter,而Content-Type的默认编码值。在类StringHttpMessageConverter中默认设置。源码如图所示:
总结:所以直接在Controller中设置响应头的Content-Type是没用的,因为还是会被MessageConverter的编码默认值给覆盖;
2. 在Controller中的@RequestMapping注解添加produces = “text/html;charset=utf-8”。代码如下所示:
@RequestMapping(value = "test",produces = "text/html;charset=utf-8")
public String testUser(HttpServletRequest req, HttpServletResponse res){
// res.setContentType("text/html;charset=utf-8");
long time=System.currentTimeMillis();
return String.format("<div><h2>测试成功。</h2><p>System.currentTimeMillis:%s</p></div>",time);
}
实现效果如下图所示:
通过以上设置,可以看到中文乱码不存在了。问题倒是解决了,但是不可能每个Controller都给这样设置吧,如果编码换成了其他呢,那改也很麻烦。
3. 自定义类去实现接口BeanPostProcessor,并重写接口方法postProcessBeforeInitialization、postProcessAfterInitialization;然后将自定义类通过bean标签装配到IOC容器中。源码如下:
//自定义类DefineCharSet
package com.comm;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.http.MediaType;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.lang.Nullable;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
public class DefineCharSet implements BeanPostProcessor {
//实例化之前调用
@Nullable
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
//实例化之后调用
@Nullable
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if(bean instanceof StringHttpMessageConverter){
MediaType mediaType = new MediaType("text", "html", Charset.forName("UTF-8"));
List<MediaType> types = new ArrayList<MediaType>();
types.add(mediaType);
((StringHttpMessageConverter) bean).setSupportedMediaTypes(types);
}
return bean;
}
}
<!--装配自定义类DefineCharSet-->
<bean class="com.comm.DefineCharSet"/>
OK,现在万事大吉,再也不用通过第2种方式去解决乱码了。