前言
在阅读本文之前,建议你已经掌握了Jackson的知识以及它的Spring、Spring Boot下的集成和运用。
说明:若不熟悉Jackson,请务必参阅我的专栏[享学Jackson](单击这里电梯直达),该专栏有可能是全网最好、最全的完整教程。
本文讲述的是本人在生产上的一个真实案例,分享给大家,避免你采坑。它的大背景是项目需要从Spring Boot1.x升级到2.x版本,升上去之后由于Jackson对时间类型序列化的变化,使得多个项目险些暴雷,幸好本人对Jackson很了解所以迅速定位并且解决问题,及时止损。
说明:因为我写这个是个脚手架,供给多个团队使用。在Jackson这点上没有考虑好向下兼容性导致多个项目差点暴雷,幸好及时止损。
正文
大家都知道,Spring Boot2.x对1.x版本是不向下兼容的,如果你曾经做过升级、或者Spring MVC -> Spring Boot2.x的迁移,相信你或多或少遇到过些麻烦。确实,Spring Boot的API设计者、代码编写者的“实力”是不如Spring Framework的,所以即使是同体系的1.x -> 2.x都会遇到不少问题(这里不包括编译问题)。
本文的关注点是Spring Boot不同大版本下Jackson对日期/时间类型的序列化问题。据我调查和了解,该问题也是很多同学的痛点,所以相信本文能帮助到你避免采坑。
Spring Boot 1.x和2.x差异
Spring Boot因它经常升级而不具有向下兼容性而向来“臭名昭著”,其中大版本号升级1.x升级到2.x尤为凸显,本文将采用这两个不同大版本,对其对日期/时间类型序列化表现作出对比。使用的Spring Boot版本号公式如下:
- 1.x版本号是:1.5.22.RELEASE(1.x版本的最后一个版本,并且在2019.8.1宣布停止维护)
- 2.x版本号是:2.0.0.RELEASE(2018.3.1发布)
说明:本文使用2.0.0.RELEASE版本,而非使用和享学Jackson 专栏一致的版本号,是想强调说明:这个差异是发生在1.x和2.x交替之时,而非2.x之后的变化。
Jar包差异
不同的Spring Boot导入的Jar版本是不一样的,这个差异在大版本号之间也不容忽略。
1.x版本:
2.x版本:
小总结
从截图方面可看出,Jar包导入方面差异还是挺大的:
- 1.x只自动给你导入了三大核心包,三个常用三方包一个都木有帮你导入
- 1.x版本最低基于JDK6构建的,所以默认其它三方包就没导入。但若你是基于JDK8构建的,强烈建议你手动导入常用三方包
- 2.x通过web带入了spring-boot-starter-json这个启动器,该启动器管理着“所有”有用的Jackson相关Jar包,不仅仅是核心包
- 2.x版本对JDK的最低要求是JDK8,所以默认就给你带上这三个常用模块是完全合理的
- 1.x使用的Jackson版本号是:2.8.11.3;2.x使用的Jackson版本号是2.9.4;版本差异上并不大,可忽略
ObjectMapper表现
我们知道Spring Boot默认情况下是向容器内放置了一个ObjectMapper实例的,因此我们可以直接使用,下面案例就是这样做的。
公用代码:
@Autowired ObjectMapper objectMapper; @Test public void contextLoads() throws JsonProcessingException { Map<String, Object> map = new LinkedHashMap<>(); map.put("date", new Date()); map.put("timestamp", new Timestamp(System.currentTimeMillis())); map.put("localDateTime", LocalDateTime.now()); map.put("localDate", LocalDate.now()); map.put("localTime", LocalTime.now()); map.put("instant", Instant.now()); System.out.println(objectMapper.writeValueAsString(map)); }
在不同的Spring Boot
版本上的输出,表现如下:
1.x版本:
{ "date":1580897613003, "timestamp":1580897613003, "localDateTime":{ "dayOfMonth":5, "dayOfWeek":"WEDNESDAY", "month":"FEBRUARY", "year":2020, "hour":18, "minute":13, "nano":9000000, "second":33, "dayOfYear":36, "monthValue":2, "chronology":{ "id":"ISO", "calendarType":"iso8601" } }, "localDate":{ "year":2020, "month":"FEBRUARY", "dayOfMonth":5, "dayOfWeek":"WEDNESDAY", "era":"CE", "chronology":{ "id":"ISO", "calendarType":"iso8601" }, "dayOfYear":36, "leapYear":true, "monthValue":2 }, "localTime":{ "hour":18, "minute":13, "second":33, "nano":9000000 }, "instant":{ "epochSecond":1580897613, "nano":9000000 } }
2.x版本:
{ "date":"2020-02-05T10:15:36.520+0000", "timestamp":"2020-02-05T10:15:36.520+0000", "localDateTime":"2020-02-05T18:15:36.527", "localDate":"2020-02-05", "localTime":"18:15:36.527", "instant":"2020-02-05T10:15:36.527Z" }
小总结
1.x的执行效果同:
@Test public void fun1() throws JsonProcessingException { ObjectMapper mapper = Jackson2ObjectMapperBuilder.json().build(); Map<String, Object> map = new LinkedHashMap<>(); map.put("date", new Date()); map.put("timestamp", new Timestamp(System.currentTimeMillis())); map.put("localDateTime", LocalDateTime.now()); map.put("localDate", LocalDate.now()); map.put("localTime", LocalTime.now()); map.put("instant", Instant.now()); System.out.println(mapper.writeValueAsString(map)); }
2.x的执行效果同:
@Test public void fun1() throws JsonProcessingException { ObjectMapper mapper = Jackson2ObjectMapperBuilder.json().build(); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); ... // 省略map(同上) System.out.println(mapper.writeValueAsString(map)); }
可以看到,他们的差异仅在一个特征值SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
是否开启。然后Spring Boot不同版本上对此值有差异:
- 1.x下此特征开启(这是Jackson的默认行为,是开启的)
- 2.x下此特征关闭