SpringMVC的HTTP序列化和反序列化核心是HttpMessageConverter
,在SSM项目中,我们需要在xml配置文件中注入MappingJackson2HttpMessageConverter
。告诉SpringMVC我们需要JSON格式的转换。
Jackson
一、[SpringBoot2.x]-Jackson在HttpMessageConverter(消息转换器)中的使用
1、简介
SpringMVC的HTTP序列化和反序列化核心是HttpMessageConverter
,在SSM项目中,我们需要在xml配置文件中注入MappingJackson2HttpMessageConverter
。告诉SpringMVC我们需要JSON格式的转换。
在SpringMVC中配置消息转换器和三方消息转换器如代码1所示:
1 | 代码1:SpringMVC中配置MappingJackson2HttpMessageConverter |
1 | <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> |
我们使用的@RequestBody
和@ResponseBody
注解,他们的作用就是将报文反序列化/序列化POJO对象。在请求时SpringMVC
会在请求头中寻找contentType
参数,然后去匹配能够处理这种类型的消息转换器。而在返回数据时,SpringMVC
根据请求头的Accept
属性,再将对象转换成响应报文。
针对于JSON这个结构,Spring默认使用Jackson来进行序列化和反序列化。在SpringBoot中会将MappingJackson2HttpMessageConverter
自动装载到IOC容器中。
1 | 源码:org.springframework.http.converter.json.MappingJackson2HttpMessageConverter |
1 |
|
而ObjectMapper
参数是Spring容器中的bean,当我们使用默认的配置对/testDate
进行http访问时。
测试代码
1 | @RequestMapping("testDate") |
正常配置.png
由上图所示,返回参数通过@ResponseBody
注解进行了消息转换
最终转化为了JSON串。实际上是借助了MappingJackson2HttpMessageConverter
来完成的。
2. 自定义输出响应内容
MappingJackson2HttpMessageConverter
借助的是Jackson来完成序列化,那么若是可以修改Jackson
的配置,便可自定义输出响应内容。
对于Date或者LocalDateTime类型,我们希望按照yyyy-MM-dd HH:mm:ss
格式输出。
借着我们对ObjectMapper
进行功能加强(设置时间类序列化格式)。注意:该段代码并未覆盖SpringBoot自动装配的ObjectMapper
对象,而是加强其配置。详情请参考——SpringBoot2.x下的ObjectMapper配置原理
1 |
|
再次请求,可以看到,得到的结果如下图所示。我们已经改变@ResponseBody对返回对象序列化的格式输出。
@ResponseBody特定的序列化格式.png
二、Spring容器中ObjectMapper
上文说到,SpringBoot2.x会自动装载MappingJackson2HttpMessageConverter
进行消息转换。而MappingJackson2HttpMessageConverter
会获取Spring容器中的ObjectMapper
配置,来进行Jackson的序列化和反序列化。
注意:在SpringBoot2.x环境下,不要将自定义的ObjectMapper对象放入Spring容器!这样会将原有的ObjectMapper配置覆盖。
ObjectMapper
是JSON操作的核心,Jackson
的JSON操作都是在ObjectMapper
中实现的。
1. SpringBoot2.x中的ObjectMapper
SpringBoot2.x默认装载了ObjectMapper到Spring容器,在yml配置文件中使用spring.jackson
为前缀的配置可以修改,也可以在代码中实现Jackson2ObjectMapperBuilderCustomizer
接口去修改ObjectMapper配置。
1 |
|
2. 修改方式
2.1 配置文件修改
1 | spring: |
2.2 java代码配置
1 |
|
三、Jackson中ObjectMapper配置详解
上文讲述了如何在SpringBoot2.x环境下去修改容器中的ObjectMapper。那么ObjectMapper提供了什么样的配置供开发人员操作?
1. ObjectMapper的参数配置
1.1 针对于ObjectMapper直接配置
源码:com.fasterxml.jackson.databind.SerializationFeature
该类是一个枚举类型,只有一个boolean
类型的参数。即开启/禁用该设置。一般我们使用ObjectMapper objectMapper = new ObjectMapper();
创建出来的ObjectMapper
对象,实际上包含了SerializationFeature
类的默认配置。我们若想修改配置,可以使用下面的方法,如代码1所示:
代码1:修改ObjectMapper中的SerializationFeature参数。
1 | //SerializationFeature代表配置;state代表状态 |
1.2 SpringBoot2.x使用Builder进行配置
我们按照SpringBoot2.x的思路,需要自定义Jackson2ObjectMapperBuilderCustomizer
子类并加入到IOC容器中。
并且在SpringBoot2.x中,引入了下面的依赖。使用builder创建ObjectMapper
时注册objectMapper.registerModule(new JavaTimeModule())
。解决了JDK1.8的新时间类LocalDateTime的问题。
1 | <dependency> |
核心代码:可以调整ObjectMapper序列化和反序列化特性。
1 |
|
官方文档参考
【2.7.0 SerializationFeature API文档】
【2.7.0 DeserializationFeature API文档】
【2.7.0 JsonGenerator.Feature API文档】
四、Jackson“默认的”时间格式化类—StdDateFormat解析
1、SpringBoot2.x环境下默认的日期格式
在SpringBoot2.x环境下,默认情况下使用com.fasterxml.jackson.databind.util.StdDateFormat
类对Date时间类进行格式化和反序列化,效果如下图所示:
1 | {"id":1001,"userName":"李白","password":"123456","age":12,"date":"2020-02-13T06:44:27.417+0000"} |
此处需要注意:Json中date与Object对象中的date相差8个小时的时差。
测试代码如下:
1 |
|
ObjectMapper提供了DateFormat
参数来处理Date类型的格式化,默认情况下,Jackson使用com.fasterxml.jackson.databind.util.StdDateFormat
类进行格式化。
时间格式为:yyyy-MM-dd'T'HH:mm:ss.SSSZ
,为ISO-8601
数据类型。
若是请求上送的Date格式为:yyyy-MM-dd HH:mm:ss
而SpringBoot使用默认的Jackson配置会出现异常:
1 | Cannot deserialize value of type `java.util.Date` from String \"2020-04-20 20:30:00\": |
解决方案:
方案1. 在yml配置中进行配置:
1 | spring: |
方案二.在实体类参数使用注解
1 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") |
2. StdDateFormat类解析
该类实现了DateFormat
接口,提供了对Date日期的序列化与反序列化的程序。
- 序列化:默认使用ISO-8601的格式(会将Date类型格式成“yyyy-MM-dd’T’HH:MM:ss.SSSZ”字符串类型)。
- 反序列化:可以解析ISO-8601(
yyyy-MM-dd'T'HH:mm:ss.SSSZ
)和RFC-1123(yyyy-MM-dd
)类型的字符串。
1 | public class StdDateFormat |
上述格式化后时间相差8个小时可以通过修改TimeZone为北京时区去解决。
1 | @Test |
2.1 Date转换为String源码分析
1 | //将Date格式化为yyyy-MM-dd'T'HH:mm:ss.SSSZ类型的数据,需要注意的是,若使用StdDateFormat那么返回的是例如:2020-02-13T06:44:27.417+0000格式的数据。 |
填充数据的公共方法(实际项目中可以利用!)。
1 | private static void pad2(StringBuffer buffer, int value) { |
2.2 String转换为Date源码分析
将String解析为Date类型的方法(可根据实际需求进行扩展)
- 根据解析是
DATE_FORMAT_STR_ISO8601(yyyy-MM-dd'T'HH:mm:ss.SSSZ)
或者DATE_FORMAT_STR_PLAIN(yyyy-MM-dd)
格式。 - 解析普通时间戳类型格式(可以允许存在负数,
可以参考代码,判断字符串是否是时间戳
)即:若上送的是时间戳,也可以解析为Date格式; - 解析
RFC1123
格式的数据;
1 | @Override |
根据字符串特定位置的字符,判断是否是ISO8601类型的格式。
1 | protected boolean looksLikeISO8601(String dateStr) |
解析ISO8601格式的字符串为Date类型。
1 | protected Date _parseAsISO8601(String dateStr, ParsePosition bogus) |
实际上StdDateFormat不会解析yyyy-MM-dd HH:mm:ss
格式的字符串。若我们上述这种类型的数据,它通过looksLikeISO8601
方法,判断其为DATE_FORMAT_STR_ISO8601
或者DATE_FORMAT_STR_PLAIN
类型的数据。
但在parseAsISO8601
方法中,会发现不存在对应类型。直接抛出异常。
五、Jackson的ObjectMapper.DefaultTyping.NON_FINAL属性
ObjectMapper
是jackson
的核心。Jackson
的Json操作都是在ObjectMapper
中实现的。ObjectMapper
有一个配置:
1 | ObjectMapper om = new ObjectMapper(); |
这个配置会对JSON的序列化和反序列化带来什么影响呢?
在SpringBoot2.x整合Redis时,若使用Jackson作为序列化工具,为何要设置该配置?
1. 源码的描述
1 | 源码:com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping |
1 | /** |
源码的解释是:对于除了一些自然类型(String、Double、Integer、Double)类型外的**非常量(non-final)**类型,类型将会用在值的含义上。以便可以在JSON串中正确的推测出值所属的类型。
2. 区别
1. 序列化的区别
我们先不设置ObjectMapper.DefaultTyping.NON_FINAL
属性。对POJO进行序列化。
1 | @Test |
输出结果如图1所示,这个是正常的JSON串。
图1_输出结果.png
接着开启objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
配置,对POJO对象进行序列化。得到的JSON串的值中带有对象的类型。这也就是源码的含义。如图2所示。
图2_开启DefaultTyping.NON_FINAL的输出配置.png
2. 反序列化的区别
我们将POJO对象进行序列化,大多数是为了存储或传输。最终我们还是需要进行反序列化成为POJO的。那么这两种JSON串进行反序列化时会有问题吗?
我们对ObjectMapper
设置了objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
参数,对普通的JSON串进行反序列化。
1 |
|
运行结果,如下列代码所示,因为我们的JSON串中不含有字段的类型,所以不能进行反序列化。
1 | com.fasterxml.jackson.databind.exc.MismatchedInputException: |
未设置objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
字段,对含有值类型的JSON串进行反序列化。
1 |
|
运行结果依旧是不能解析。
1 | com.fasterxml.jackson.databind.exc.MismatchedInputException: |
3. 注意事项
Spring源码中是使用容器中的ObjectMapper
对象进行序列化和反序列化。当我们将自定义的ObjectMapper对象放入IOC容器中后,会自动覆盖SpringBoot自动装载的ObjectMapper对象。若是我们在自定义的ObjectMapper
中设置了objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
属性。那么可能会影响我们的@RequestBody
反序列化JSON串,导致Spring Boot默认错误返回格式变成数组@RequestBody无法解析Json格式异常。
如何去自定义配置IOC容器中的ObjectMapper
对象,实际上,SpringBoot给了两种方式,详情请参考:SpringBoot配置ObjectMapper源码,以及ObjectMapper序列化/反序列化配置。
4. 项目运用
SpringBoot2.X整合Redis缓存中,使用Jackson作为序列化工具,其中就使用了ObjectMapper.DefaultTyping.NON_FINAL
配置。
1 | Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); |
因为数据在反序列化为具体类时,需要传入具体的对象类型。而Redis的序列化配置是公共配置。我们只能传入Object.class
类型进行反序列化。
序列化得到的字段为
序列化对象格式.png
反序列化会得到LinkedHashMap类型,该类型不能强转为具体的对象类型,即抛出
1 | java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.galax.pojo.Account |
- 所以我们需要
ObjectMapper.DefaultTyping.NON_FINAL
配置,在序列化时记录对象类型,以便反序列化时得到对应的具体对象。
六、引用
1 | https://www.jianshu.com/p/560e9b114c29 |
- 本文作者: 初心
- 本文链接: http://funzzz.fun/2021/01/04/jackson---3springBoot/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!