返回類型為ResponseEntity<T>
代表我們返回的數據是一個對象,在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>