- 问题描述 SpringBoot项目中当前台通过ajax传入日期字符串如:2018-10-01 00:00:00至后台 Date 字段时,部分异常信息如下:
org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Can not deserialize value of type java.util.Date from String "2018-10-01 00:00:00": not a valid representation (error: Failed to parse Date value '2018-10-01 00:00:00': Can not parse date "2018-10-01 00:00:00Z": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSS'Z'', parsing fails (leniency? null)) at [Source: java.io.PushbackInputStream@57e813aa; line: 1, column: 132] (through reference chain: com.shenma.romp.service.dro.CouponActivityDro["startTime"]); nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not deserialize value of type java.util.Date from String "2018-10-11 00:00:00": not a valid representation (error: Failed to parse Date value '2018-10-11 00:00:00': Can not parse date "2018-10-11 00:00:00Z": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSS'Z'', parsing fails (leniency? null)) at [Source: java.io.PushbackInputStream@57e813aa; line: 1, column: 132] (through reference chain: com.shenma.romp.service.dro.CouponActivityDro["startTime"]) at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:240) at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:225) at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:201) at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:150) at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:128) at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121) at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:158) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:116) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
- 根源 从异常信息中我们可以看出,是在AbstractJackson2HttpMessageConverter类中调用了readJavaType方法之后抛的异常,最终在类DeserializationContext 中找到问题
public Date parseDate(String dateStr) throws IllegalArgumentException{ try { DateFormat df = getDateFormat(); return df.parse(dateStr); } catch (ParseException e) { throw new IllegalArgumentException(String.format("Failed to parse Date value '%s': %s", dateStr,e.getMessage())); }}
再往下 DateFormat 来源于 MapperConfig
public final DateFormat getDateFormat() { return _base.getDateFormat(); }
SpringMvc是通过AbstractJackson2HttpMessageConverter类来整合jackson的,该类维护jackson的ObjectMapper,而ObjectMapper又是通过MapperConfig来进行配置的,所有本异常就是因为ObjectMapper中的DateFormat无法对yyyy-MM-dd HH:mm:ss格式的字符串进行转换所导致的
-
问题处理 首先我们先使用装饰模式来创建一个支持yyyy-MM-dd HH:mm:ss格式的DateFormat如下 `public class MyDateFormat extends DateFormat {
private static final long serialVersionUID = -4580955831439573829L;
private DateFormat dateFormat;
private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public MyDateFormat(DateFormat dateFormat) { this.dateFormat = dateFormat; }
@Override public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) { return dateFormat.format(date, toAppendTo, fieldPosition); }
@Override public Date parse(String source, ParsePosition pos) { Date date = null; try { date = simpleDateFormat.parse(source, pos); } catch (Exception e) {
date = dateFormat.parse(source, pos); } return date;
}
@Override public Date parse(String source) throws ParseException { Date date = null; try { date = simpleDateFormat.parse(source); } catch (Exception e) {
date = dateFormat.parse(source); } return date;
}
@Override public Object clone() { Object format = dateFormat.clone(); return new MyDateFormat((DateFormat) format); }
}接下来的任务就是让ObjectMapper使用我的这个DateFormat了,在config类中定义如下
@Configuration public class JacksonHttpMessageConvertersConfig {
@Autowired
private Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder;
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
ObjectMapper mapper = jackson2ObjectMapperBuilder.build();
DateFormat dateFormat = mapper.getDateFormat();
mapper.setDateFormat(new MyDateFormat(dateFormat));
MappingJackson2HttpMessageConverter mappingJsonpHttpMessageConverter = new MappingJackson2HttpMessageConverter(mapper);
return mappingJsonpHttpMessageConverter;
}
}` 配置了上述代码之后,问题成功解决。
-
为什么往spring容器中注入MappingJackson2HttpMessageConverter,springMvc就会用这个Converter呢? 查看springboot的源代码如下 `@Configuration class JacksonHttpMessageConvertersConfiguration {
@Configuration @ConditionalOnClass(ObjectMapper.class) @ConditionalOnBean(ObjectMapper.class) @ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "jackson", matchIfMissing = true) protected static class MappingJackson2HttpMessageConverterConfiguration {
@Bean @ConditionalOnMissingBean(value = MappingJackson2HttpMessageConverter.class, ignoredType = { "org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter", "org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter" }) public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter( ObjectMapper objectMapper) { return new MappingJackson2HttpMessageConverter(objectMapper); }
} }` 当spring容器中没有MappingJackson2HttpMessageConverter这个实例的时候才会被创建
【说明】该解决方案来源于网上资源