最近项目中不同微服务之间的LocalDateTime时间交互,格式上出现了差异,一个服务入参返参都是默认的 yyyy-MM-dd'T'HH:mm:ss
,一个服务定义了全局时间格式,入参与返参是 yyyy-MM-dd HH:mm:ss
,导致服务调用失败。正常情况下,所有服务应该做到格式统一,今天我就从源码上,来分析下全局定义时间格式的几种方式。
先聊一下 Date 的处理,Jackson 配置中包含了对 Date 的格式处理,可以通过 spring.jaskson.dateformat
进行修改,主要在 yaml 中配置时间格式、时区、国家
yamlspring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
locale: zh_CN
根据定义的 dateFormat,在Spring容器中查找此类型的Bean实例信息,如果找到,调用 #dateFormat 方法。
如果找不到,就会使用默认的 SimpleDateFormat 进行处理。
通过以上的源码分析得出,我们可以自定义对 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;
}
}
配置:
yamlspring:
jackson:
date-format: cn.liushigong.boot.MyDateFormat
time-zone: GMT+8
locale: zh_CN
Date 说完了,来说一下Java8 以后的时间处理 LocalDateTime。
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 构建的,找到此对象的配置信息
从以上代码分析得知,Jackson2ObjectMapperBuilder 对象在创建的时候,通过注入 Jackson2ObjectMapperBuilderCustomizer 的Bean实例信息,循环调用 #customize 方法
java@FunctionalInterface
public interface Jackson2ObjectMapperBuilderCustomizer {
/**
* Customize the JacksonObjectMapperBuilder.
* @param jacksonObjectMapperBuilder the JacksonObjectMapperBuilder to customize
*/
void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder);
}
通过此接口,查找到默认的实现类
了解其思路后,我们需要对 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 的引用有哪些。
在茫茫代码中,我立马就看到了 WebMvcConfigurationSupport 配置类,回想到 SpringMVC 的执行流程,结合源码,猜测是在消息转换处理器中进行的数据转换操作。
从上面源码中,可以看到 MappingJackson2HttpMessageConverter 消息转换器在创建的时候,注入了一个对象 ObjectMapper,此对象是从 Jackson2ObjectMapperBuilder 中构建出来的
查看 MappingJackson2HttpMessageConverter 源码中对 ObjectMapper 的处理,在父类找到以下代码
此时串下思路:
通过 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 时,注入到此对象中
那我们是否可以直接 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 许可协议。转载请注明出处!