博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
springcloud采坑-jason序列化中的Date对象
阅读量:4207 次
发布时间:2019-05-26

本文共 11670 字,大约阅读时间需要 38 分钟。

提纲:

    1.出现的场景。

    2.报错内容和代码追踪。

    3.原因。

    4.三种解决方案。

1.    出现的场景:

  •     服务端提供一个springcloud接口。
  •     客户端通过feign调用该接口,返回值为一个列表的CompanyDTO,DTO中有一个Date对象。
  •     调用出错。

2.    报错如下:

Caused by: org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Can not deserialize value of type java.util.Date from String "2017-12-08 09:20:53": not a valid representation (error: Failed to parse Date value '2017-12-08 09:20:53': Can not parse date "2017-12-08 09:20:53Z": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSS'Z'', parsing fails (leniency? null)) at [Source: java.io.PushbackInputStream@2041c1b0; line: 1, column: 554] (through reference chain: com.qts.base.result.ResponseData["data"]->java.util.ArrayList[0]->com.qts.company.api.dto.CompanyDTO["applyRefundTime"]); nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not deserialize value of type java.util.Date from String "2017-12-08 09:20:53": not a valid representation (error: Failed to parse Date value '2017-12-08 09:20:53': Can not parse date "2017-12-08 09:20:53Z": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSS'Z'', parsing fails (leniency? null)) at [Source: java.io.PushbackInputStream@2041c1b0; line: 1, column: 554] (through reference chain: com.qts.base.result.ResponseData["data"]->java.util.ArrayList[0]->com.qts.company.api.dto.CompanyDTO["applyRefundTime"])	at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:228)	at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:213)	at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:95)	at org.springframework.cloud.netflix.feign.support.SpringDecoder.decode(SpringDecoder.java:59)	at org.springframework.cloud.netflix.feign.support.ResponseEntityDecoder.decode(ResponseEntityDecoder.java:47)	at feign.SynchronousMethodHandler.decode(SynchronousMethodHandler.java:165)	... 75 moreCaused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not deserialize value of type java.util.Date from String "2017-12-08 09:20:53": not a valid representation (error: Failed to parse Date value '2017-12-08 09:20:53': Can not parse date "2017-12-08 09:20:53Z": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSS'Z'', parsing fails (leniency? null)) at [Source: java.io.PushbackInputStream@2041c1b0; line: 1, column: 554] (through reference chain: com.qts.base.result.ResponseData["data"]->java.util.ArrayList[0]->com.qts.company.api.dto.CompanyDTO["applyRefundTime"])	at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:74)	at com.fasterxml.jackson.databind.DeserializationContext.weirdStringException(DeserializationContext.java:1410)	at com.fasterxml.jackson.databind.DeserializationContext.handleWeirdStringValue(DeserializationContext.java:926)	at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseDate(StdDeserializer.java:822)	at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseDate(StdDeserializer.java:791)	at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateBasedDeserializer._parseDate(DateDeserializers.java:172)	at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateDeserializer.deserialize(DateDeserializers.java:259)	at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateDeserializer.deserialize(DateDeserializers.java:242)	at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:499)	at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:101)	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:357)	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:148)	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:277)	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:249)	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:26)	at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:499)	at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:101)	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:357)	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:148)	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3789)	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2913)	at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:225)	... 80 more

  • 从AbstractJackson2HttpMessageConverter.java:225的readJavaType方法开始。
  • DateDeserializers.java的deserialize中,有一段核心逻辑:this._customFormat.parse(str)。问题来了:_customFormat是从哪里来的?
protected Date _parseDate(JsonParser p, DeserializationContext ctxt) throws IOException {            if (this._customFormat != null) {                JsonToken t = p.getCurrentToken();                if (t == JsonToken.VALUE_STRING) {                    String str = p.getText().trim();                    if (str.length() == 0) {                        return (Date)this.getEmptyValue(ctxt);                    }                    DateFormat var5 = this._customFormat;                    synchronized(this._customFormat) {                        Date var10000;                        try {                            var10000 = this._customFormat.parse(str);//重点                        } catch (ParseException var8) {                            return (Date)ctxt.handleWeirdStringValue(this.handledType(), str, "expected format \"%s\"", new Object[]{this._formatString});                        }                        return var10000;                    }                }            }            return super._parseDate(p, ctxt);        }

  •     DateBasedDeserializer中的_customFormat,通过ctxt.getConfig()获取,DeserializationContext是配置的上下文。
  • protected abstract static class DateBasedDeserializer
    extends StdScalarDeserializer
    implements ContextualDeserializer { protected final DateFormat _customFormat; protected final String _formatString; protected abstract DateDeserializers.DateBasedDeserializer
    withDateFormat(DateFormat var1, String var2); public JsonDeserializer
    createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { if (property != null) { Value format = this.findFormatOverrides(ctxt, property, this.handledType()); if (format != null) { TimeZone tz = format.getTimeZone(); Locale loc; if (format.hasPattern()) { String pattern = format.getPattern(); loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale(); SimpleDateFormat df = new SimpleDateFormat(pattern, loc); if (tz == null) { tz = ctxt.getTimeZone(); } df.setTimeZone(tz); return this.withDateFormat(df, pattern); } if (tz != null) { DateFormat df = ctxt.getConfig().getDateFormat();//重点 Object df; if (df.getClass() == StdDateFormat.class) { loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale(); StdDateFormat std = (StdDateFormat)df; std = std.withTimeZone(tz); std = std.withLocale(loc); df = std; } else { df = (DateFormat)df.clone(); ((DateFormat)df).setTimeZone(tz); } return this.withDateFormat((DateFormat)df, this._formatString); } } } return this; } }
  •     我们看下getDateFormat的具体实现,最终是通过MapperConfig中的BaseSettings获取。
public final DateFormat getDateFormat() {        return this._base.getDateFormat();    }

3.具体原因是Jason只支持一下几种格式:

"yyyy-MM-dd'T'HH:mm:ss.SSSZ"   "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"   "EEE, dd MMM yyyy HH:mm:ss zzz"   "yyyy-MM-dd"

4.解决方案:

    既然默认的序列化不支持这样的格式,那就手动设置一下。

    4.1设置客户端的DateFormat。

public class MyDateFormat extends DateFormat {    private DateFormat dateFormat;    private SimpleDateFormat format1 = new SimpleDateFormat("yyy-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 = format1.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 = format1.parse(source);        } catch (Exception e) {            // 不行,那就按原先的规则吧            date = dateFormat.parse(source);        }        return date;    }    // 这里装饰clone方法的原因是因为clone方法在jackson中也有用到    @Override    public Object clone() {        Object format = dateFormat.clone();        return new MyDateFormat((DateFormat) format);    }}

    并在启动的时候加入。

@Configurationpublic class WebConfig {	@Autowired	private Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder;		@Bean	public MappingJackson2HttpMessageConverter MappingJsonpHttpMessageConverter() {		ObjectMapper mapper = jackson2ObjectMapperBuilder.build();		// ObjectMapper为了保障线程安全性,里面的配置类都是一个不可变的对象		// 所以这里的setDateFormat的内部原理其实是创建了一个新的配置类		DateFormat dateFormat = mapper.getDateFormat();		mapper.setDateFormat(new MyDateFormat(dateFormat));		MappingJackson2HttpMessageConverter mappingJsonpHttpMessageConverter = new MappingJackson2HttpMessageConverter(				mapper);		return mappingJsonpHttpMessageConverter;	}}
4.2解决方案2

    可以通过代码,那就可以通过配置。。。

#spring.mvc.date-format=yyyy-MM-dd HH:mm:ss#spring.jackson.time-zone=GMT+8#spring.jackson.date-format=yyyy-MM-dd HH:mm:ss#spring.jackson.deserialization.accept_empty_string_as_null_object=true

4.3解决方案3

    在字段的setter上加上注解,这种方法比较麻烦。

public class DateJsonDeserializer extends JsonDeserializer
{ /** * @see JsonDeserializer#deserialize(JsonParser, * DeserializationContext) */ @Override public Date deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException { try { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.parse(parser.getValueAsString()); } catch (Exception e) { e.printStackTrace(); } return null; }}

然后在DTO的字段上增加注解:

@JsonDeserialize(using = DateJsonDeserializer.class)    private Date applyRefundTime;

参考文章:

https://blog.csdn.net/qq906627950/article/details/79503801

http://wujiu.iteye.com/blog/2244537

你可能感兴趣的文章
面试刷题27:程序员如何防护java界的新冠肺炎?
查看>>
面试刷题28:如何写出安全的java代码?
查看>>
面试刷题29:mysql事务隔离实现原理?
查看>>
面试刷题30:SpringBean的生命周期?
查看>>
面试刷题31:分布式ID设计方案
查看>>
面试刷题32:你对tomcat做了哪些性能调优?
查看>>
面试刷题33:如何应对高并发?
查看>>
面试刷题34:说一下分布式架构中的缓存使用场景?
查看>>
面试刷题35:负载均衡有哪几种实现方式?
查看>>
面试刷题36:线程池的原理和使用方法?
查看>>
面试刷题36:线程池的原理和使用方法?
查看>>
面试刷题37:微服务是什么?springcloud,springboot是什么?
查看>>
技术面试要做哪些准备?
查看>>
kafka的基本体系结构
查看>>
kafka消息分区机制原理
查看>>
kafka高吞吐量之消息压缩
查看>>
如何保证kafka消息不丢失
查看>>
如何保证kafka消息不丢失
查看>>
请设计一个核心功能稳定适合二开扩展的软件系统
查看>>
kafka可插拔增强如何实现?
查看>>