<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的工作流程。