2023-08-09
实战设计
0

目录

Date 的处理
源码中的处理
LocalDateTime 的处理
@JsonFormat
全局格式化
思考
衍生

最近项目中不同微服务之间的LocalDateTime时间交互,格式上出现了差异,一个服务入参返参都是默认的 yyyy-MM-dd'T'HH:mm:ss,一个服务定义了全局时间格式,入参与返参是 yyyy-MM-dd HH:mm:ss,导致服务调用失败。正常情况下,所有服务应该做到格式统一,今天我就从源码上,来分析下全局定义时间格式的几种方式。

Date 的处理

先聊一下 Date 的处理,Jackson 配置中包含了对 Date 的格式处理,可以通过 spring.jaskson.dateformat 进行修改,主要在 yaml 中配置时间格式、时区、国家

yaml
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 locale: zh_CN

源码中的处理

根据定义的 dateFormat,在Spring容器中查找此类型的Bean实例信息,如果找到,调用 #dateFormat 方法。

如果找不到,就会使用默认的 SimpleDateFormat 进行处理。

image.png

通过以上的源码分析得出,我们可以自定义对 Date 格式的处理,只需要继承 DateFormat 在 Jackson 中的实现类 StdDateFormat ,配置yaml属性为该处理类的包路径即可。

java
@Component public class MyDateFormat extends StdDateFormat { @Override public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) { return null; } @Override public Date parse(String source, ParsePosition pos) { return null; } }

配置:

yaml
spring: jackson: date-format: cn.liushigong.boot.MyDateFormat time-zone: GMT+8 locale: zh_CN

LocalDateTime 的处理

Date 说完了,来说一下Java8 以后的时间处理 LocalDateTime。

@JsonFormat

LocalDateTime 默认的返回格式是 yyyy-MM-dd'T'HH:mm:ss,我们可以通过使用 @JsonFormat 注解修饰属性,进行格式化处理,不过当所有的时间都需要格式处理的时候,此种方式太过繁琐,我们需要一种与 Date 一样的全局处理的方式。

java
@JsonFormat(pattern = "yyyy-MM-dd HH:mm", timezone = "GMT+8") private LocalDateTime createTime;

全局格式化

查看Jackson源码,发现其对类型序列化是通过 Jackson2ObjectMapperBuilder 构建的,找到此对象的配置信息

image.png

从以上代码分析得知,Jackson2ObjectMapperBuilder 对象在创建的时候,通过注入 Jackson2ObjectMapperBuilderCustomizer 的Bean实例信息,循环调用 #customize 方法

java
@FunctionalInterface public interface Jackson2ObjectMapperBuilderCustomizer { /** * Customize the JacksonObjectMapperBuilder. * @param jacksonObjectMapperBuilder the JacksonObjectMapperBuilder to customize */ void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder); }

通过此接口,查找到默认的实现类

image.png

了解其思路后,我们需要对 Jackson2ObjectMapperBuilder 进行扩展,添加日期格式的序列化与反序列化,就可以通过注册 Jackson2ObjectMapperBuilderCustomizer Bean实例对象进行处理,代码如下

java
@Configuration public class LocalDateTimeConfig { @Bean public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { return builder -> // 序列化与反序列化日期时间格式 builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN))) .serializerByType(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN))) .serializerByType(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN))) .deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN))) .deserializerByType(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN))) .deserializerByType(LocalTime.class, new LocalTimeDeserializer(DatePattern.NORM_TIME_FORMATTER)); } }

思考

思考

注册了 Jackson2ObjectMapperBuilder 的 Bean 实例对象,他是在哪里被使用,又是如何处理 Controller 中接口返回的数据呢?

带着疑问,我们继续分析,查看 Jackson2ObjectMapperBuilder 的引用有哪些。

image.png

在茫茫代码中,我立马就看到了 WebMvcConfigurationSupport 配置类,回想到 SpringMVC 的执行流程,结合源码,猜测是在消息转换处理器中进行的数据转换操作。

image.png

从上面源码中,可以看到 MappingJackson2HttpMessageConverter 消息转换器在创建的时候,注入了一个对象 ObjectMapper,此对象是从 Jackson2ObjectMapperBuilder 中构建出来的

image.png

查看 MappingJackson2HttpMessageConverter 源码中对 ObjectMapper 的处理,在父类找到以下代码

image.png

此时串下思路:

通过 Jackson2ObjectMapperBuilder 添加序列化信息,而 Jackson2ObjectMapperBuilder #build() 方法构建出 ObjectMapper,ObjectMapper 添加在 HttpMessageConverter 消息转换器中。

结合源码想到在 WebMvcConfigurationSupport( 或者是 WebMvcConfigurer ) 中,可以手动实现 #extendMessageConverters 方法,对内置的消息处理器进行管理,而消息转换器又可以单独设置 ObjectMapper 对象,那我们就可以替换代码中默认生成的 ObjectMapper,手动创建 Jackson2ObjectMapperBuilder 对象去重构 ObjectMapper,代码如下:

java
@Configuration public class WebMvcConfig implements WebMvcConfigurer { /** * Override this method to extend or modify the list of converters after it has * been configured. This may be useful for example to allow default converters * to be registered and then insert a custom converter through this method. * * @param converters the list of configured converters to extend * @since 4.1.3 */ @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { // 重构Jackson对象映射器的序列化与反序列化日期时间格式 converters.stream().filter(x -> MappingJackson2HttpMessageConverter.class.isAssignableFrom(x.getClass())).findFirst().ifPresent(x -> { MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = (MappingJackson2HttpMessageConverter) x; // 自定义Jackson对象映射器构建器定制器 Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json(); // 序列化与反序列化日期时间格式 builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN))); builder.serializerByType(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN))); builder.serializerByType(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN))); builder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN))); builder.deserializerByType(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN))); builder.deserializerByType(LocalTime.class, new LocalTimeDeserializer(DatePattern.NORM_TIME_FORMATTER)); // 对象映射器 mappingJackson2HttpMessageConverter.setObjectMapper(builder.build()); }); } }

经过测试,以上代码也可实现全局的日期格式处理,可以省略了Bean注册信息的过程。

衍生

在从源码中衍生,就会发现创建的序列化对象存储在 Jackson2ObjectMapperBuilder serializers 属性中,通过构建 ObjectMapper 时,注入到此对象中

image.png

那我们是否可以直接 new ObjectMapper() 此对象,然后将序列化对象添加到 SimpleModule 中

java
@Configuration public class WebMvcConfig implements WebMvcConfigurer { /** * Override this method to extend or modify the list of converters after it has * been configured. This may be useful for example to allow default converters * to be registered and then insert a custom converter through this method. * * @param converters the list of configured converters to extend * @since 4.1.3 */ @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { // 重构Jackson对象映射器的序列化与反序列化日期时间格式 converters.stream().filter(x -> MappingJackson2HttpMessageConverter.class.isAssignableFrom(x.getClass())).findFirst().ifPresent(x -> { MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = (MappingJackson2HttpMessageConverter) x; // 模块 SimpleModule module = new SimpleModule(); module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN))); module.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN))); module.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN))); module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN))); module.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN))); module.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DatePattern.NORM_TIME_FORMATTER)); // 注册模块 ObjectMapper objectMapper = mappingJackson2HttpMessageConverter.getObjectMapper(); objectMapper.registerModule(module); }); } }

以上是根据源码分析出的3种全局处理日期格式的方案,本质就是找到切入点替换默认的序列化格式,实现方式千千万,没有最优,只有更优。

本文作者:柳始恭

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!