SpringMVC使用Jackson返回JSON時日期少一天的問題

1、原來爲解決IE中出現下載JSON的問題,手動配置了HttpMessageConverter
<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的工作流程。








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章