<mvc:annotation-driven validator="validator" conversion-service="conversionService" content-negotiation-manager="contentNegotiationManager" enableMatrixVariables="true">
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg ref="encoding"/>
<property name="supportedMediaTypes">
<list>
<bean class="org.springframework.http.MediaType">
<constructor-arg index="0" value="text"/>
<constructor-arg index="1" value="plain"/>
<constructor-arg index="2" ref="UTF-8"/>
</bean>
<bean class="org.springframework.http.MediaType">
<constructor-arg index="0" value="*"/>
<constructor-arg index="1" value="*"/>
<constructor-arg index="2" ref="UTF-8"/>
</bean>
</list>
</property>
</bean>
<ref bean="jacksonMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
<!-- JSON轉換器,避免IE出現下載JSON文件的情況 -->
<bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
2、使用時發現解析Date日期都是Long類型,想解析爲字符串,查看文檔,在Date字段上加@JsonFormat
class Demo
{
private String name;
private Integer age;
@JsonFormat(pattern = "yyyy-MM-dd")
private Date birthday;
}
但是查看結果後,發現日期少一天。查看源碼發現要加上時區@JsonFormat(pattern = "yyyy-MM-dd",timezone="GMT+08")(東八區)。但是發現這樣很麻煩,如果日期字段多的話就很費事,並且不想在字段上限定太死。
因爲使用Jackson解析,就查看Jackson解析的代碼,發現使用類ObjectMapper。查看源碼在ObjectMapper中可以設置時區,就手動配置ObjectMapper並設置了時區
<bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
</list>
</property>
<property name="objectMapper" ref="objectMapper" />
</bean>
<bean id="objectMapper" class="com.fasterxml.jackson.databind.ObjectMapper">
<property name="timeZone">
<bean class="java.util.TimeZone" factory-method="getTimeZone" >
<constructor-arg value="GMT+08"/>
</bean>
</property>
</bean>
但是配置後發現還是少一天。頭都大了,怎麼辦呢?
於是Debug看看流程,發現在正真解析時用的MappingJackson2HttpMessageConverter不是手動配置的MappingJackson2HttpMessageConverter實例,怎麼回事呢?
查看源碼,發現問題處在<mvc:message-converters register-defaults="true"> ,register-defaults是是否註冊默認的轉換器,SpringMVC默認註冊了7種轉換器,加上自定義的兩個總共9個,自定義的在前面,默認的在後面(存放在List集合)。
這樣的話將register-defaults設爲false就可以解決了,測試後發現OK。
3、但是還有問題,照上面所說的在執行時默認應該是拿前面的啊,怎麼會拿後面的?怎麼回事?只能查看源碼了。
於是Debug了下,看看SpingMVC的代碼執行過程。找到SpingMVC在響應解析內容時,獲取轉換器在org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor的writeWithMessageConverters方法中,
protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException {
Class<?> returnValueClass = returnValue.getClass();//返回值類型
HttpServletRequest servletRequest = inputMessage.getServletRequest();
//application/json, text/javascript, */*;q=0.01
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);//獲取客戶端瀏覽器支持的類型
//[text/html;charset=UTF-8, application/json;charset=UTF-8, application/json;charset=UTF-8, application/*+json;charset=UTF-8]
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);//根據返回值和請求獲取可生產的類型
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();//兼容的類型
for (MediaType requestedType : requestedMediaTypes) {
for (MediaType producibleType : producibleMediaTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));//增加兼容類型
}
}
}
if (compatibleMediaTypes.isEmpty()) {
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);//沒有可支持的類型,拋出異常
}
List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
MediaType.sortBySpecificityAndQuality(mediaTypes);
//[application/json;charset=UTF-8, text/html;charset=UTF-8;q=0.01, application/json;charset=UTF-8;q=0.01, application/*+json;charset=UTF-8;q=0.01]
MediaType selectedMediaType = null;//選擇的具體類型
for (MediaType mediaType : mediaTypes) {
if (mediaType.isConcrete()) {//是否是具體的類型
selectedMediaType = mediaType;
break;
}
else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
//是*/* 或 application,則設置爲默認的類型 application/octet-stream
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (selectedMediaType != null) {
//application/json;charset=UTF-8
selectedMediaType = selectedMediaType.removeQualityValue();//去除q=?
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {//判斷轉換器是否支持選擇的類型
//獲取到具體的轉換器,向客戶端發送信息(轉換後的信息),具體的轉換工作有轉換器完成
((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +
messageConverter + "]");
}
return;
}
}
}
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
發現預先支持的是application/json;charset=UTF-8,而我自己配置的不支持application/json類型,於是改爲
<bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
<property name="objectMapper" ref="objectMapper" />
</bean>
就OK了。問題解決,也不用設置時區了。
4、當然還可以給ObjectMapper設置DateFormat,全局統一設置日期轉換格式。需要個性化設置的地方在設置@JsonFormat就可以
<bean id="objectMapper" class="com.fasterxml.jackson.databind.ObjectMapper">
<property name="timeZone">
<bean class="java.util.TimeZone" factory-method="getTimeZone" >
<constructor-arg value="GMT+08"/>
</bean>
</property>
<property name="dateFormat">
<bean class="java.text.SimpleDateFormat">
<constructor-arg type="java.lang.String" value="yyyy-MM-dd"/>
</bean>
</property>
</bean>
最終配置
<mvc:annotation-driven validator="validator" conversion-service="conversionService" content-negotiation-manager="contentNegotiationManager" enableMatrixVariables="true">
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg ref="encoding"/>
<property name="supportedMediaTypes">
<list>
<bean class="org.springframework.http.MediaType">
<constructor-arg index="0" value="text"/>
<constructor-arg index="1" value="plain"/>
<constructor-arg index="2" ref="UTF-8"/>
</bean>
<bean class="org.springframework.http.MediaType">
<constructor-arg index="0" value="*"/>
<constructor-arg index="1" value="*"/>
<constructor-arg index="2" ref="UTF-8"/>
</bean>
</list>
</property>
</bean>
<span style="white-space:pre"> </span><ref bean="jacksonMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
<bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
<property name="objectMapper" ref="objectMapper" />
</bean>
<bean id="objectMapper" class="com.fasterxml.jackson.databind.ObjectMapper">
<property name="timeZone">
<bean class="java.util.TimeZone" factory-method="getTimeZone" >
<constructor-arg value="GMT+08"/>
</bean>
</property>
<property name="dateFormat">
<bean class="java.text.SimpleDateFormat">
<constructor-arg type="java.lang.String" value="yyyy-MM-dd"/>
</bean>
</property>
</bean>
解決了SpringMVC響應JSON的日期Date類型的問題,又進一步瞭解了SpringMVC的工作流程。