SpringMVC中controller中方法返回类型为ResponseEntity乱码的问题
SpringMVC中controller中方法返回类型为ResponseEntity乱码的问题
返回类型为ResponseEntity
代表我们返回的数据是一个对象,在springMVC中,请求数据到对象和对象到响应数据的转换是通过消息转换器来完成的。
HttpMessageConverter是消息转换器的顶层接口,所有的消息转换器都必须实现这个接口
package org.springframework.http.converter;
import java.io.IOException;
import java.util.List;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, MediaType mediaType);
boolean canWrite(Class<?> clazz, MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
我们可以看到针对不同的类型,实现了具体消息转换器
下面先来说说mvc:annotation-driven</mvc:annotation-driven>,通过这个线索一直下去,找到问题的根源。
通常我们在springMVC配置文件中会配置mvc:annotation-driven</mvc:annotation-driven>注解驱动,它对应的类是AnnotationDrivenBeanDefinitionParser
在 spring-webmvc.jar包下我们可以找到AnnotationDrivenBeanDefinitionParser类
分析getMessageConverters()方法后发现执行流程如下:
1.它首先会去配置中查找mvc:annotation-driven父标签中是否含有mvc:message-converters子标签,如果有则把mvc:message-converters下的所有自定义消息转化器封装在message-converter对象中
2.然后判断message-converter是否为空。如果不为空,则把message-converter中封装的的所有自定义消息转换器添加到managedList集合中。
3.a)如果mvc:annotation-driven的属性register-defaults为真,还会加载默认列出的消息转换器,并加入到managedList集合里。b)如果为假,则只会加载我们自定义的。tips:通常我们都会是它为真,或不配置这个属性,不配置,他默认为真。
class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
…
private ManagedList<?> getMessageConverters(Element element, Object source, ParserContext parserContext) {
Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters");
ManagedList<? super Object> messageConverters = new ManagedList<Object>();
if (convertersElement != null) {
messageConverters.setSource(source);
for (Element beanElement : DomUtils.getChildElementsByTagName(convertersElement, "bean", "ref")) {
Object object = parserContext.getDelegate().parsePropertySubElement(beanElement, null);
messageConverters.add(object);
}
}
if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) {
messageConverters.setSource(source);
messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.class, source));
RootBeanDefinition stringConverterDef = createConverterDefinition(StringHttpMessageConverter.class, source);
stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);
messageConverters.add(stringConverterDef);
messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source));
messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source));
messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source));
if (romePresent) {
messageConverters.add(createConverterDefinition(AtomFeedHttpMessageConverter.class, source));
messageConverters.add(createConverterDefinition(RssChannelHttpMessageConverter.class, source));
}
if (jackson2XmlPresent) {
RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2XmlHttpMessageConverter.class, source);
GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);
jacksonFactoryDef.getPropertyValues().add("createXmlMapper", true);
jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);
messageConverters.add(jacksonConverterDef);
}
else if (jaxb2Present) {
messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
}
if (jackson2Present) {
RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2HttpMessageConverter.class, source);
GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);
jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);
messageConverters.add(jacksonConverterDef);
}
else if (gsonPresent) {
messageConverters.add(createConverterDefinition(GsonHttpMessageConverter.class, source));
}
}
return messageConverters;
}
}
从其中我们可以找到添加StringHttpMessageConverter转换器的代码段
和之前输出JSON用到的MappingJackson2HttpMessageConverter转换器,这也可以看处springMVC对JSON的支持为什么要加入jackson的依赖。
我这里为什么要单独挑出这两个转换器,是为了说明他们的区别和调用时机的,和进一步引出问题。
a)之前如果我们要把对象以JSON格式输出,可以给controller的方法加@ResponseBody注解,到后来我们依照resultful的思想来做项目时,函数的返回值不会再是POJO,而是ResponseEntity<T>,有了它我们向往前台输出对象对应的JSON就不再需要再添加@ResponseBody注解了
b)在SpringMVC中是怎么把我们的对象转换为JSON输出的呢,这里依旧要提到spring mvc的消息转换器,是它完成了这个转换过程。
c)但是函数值为ResponseEntity<T>并不是所有的对象都会再后来转换为json,比如String,它会调用对应的StringHttpMessageConverter的消息转换器,而不是MappingJackson2HttpMessageConverter转换器。所以如果返回值类型为ResponseEntity<String> ,要输出json需要我们自己把json数据写到string里,spring是不会帮我们转换的。
接下来我们看StringHttpMessageConverter.class类
org.springframework.http.converter.StringHttpMessageConverter
public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {
public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");
private final Charset defaultCharset;
…
public StringHttpMessageConverter() {
this(DEFAULT_CHARSET);
}
public StringHttpMessageConverter(Charset defaultCharset) {
super(new MediaType("text", "plain", defaultCharset), MediaType.ALL);
this.defaultCharset = defaultCharset;
this.availableCharsets = new ArrayList<Charset>(Charset.availableCharsets().values());
}
…
}
发现它的默认转换编码是iso-8859-1,前台解码一般用utf-8,编解码不一致导致乱码,但我们发现这给类的构造方法预留了一个参数让我自己指定编码,这里我们看到了一丝希望哈哈。
解决办法:
我们只要在spring-mvc.xml配置文件中自己为这个消息转换器配置utf-8编码就ok了
<mvc:annotation-driven>
***<!-- 自定义消息转换器 -->***
<mvc:message-converters register-defaults="true">
***<!-- 自定义消息转换器,设置编码为utf-8,防止responseEntity<String>转换成json对象输出乱码 -->***
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg index="0" value="utf-8"></constructor-arg>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>