SpringBoot之Jackson,自动化配置,Java 8 date/time type java.time.LocalTime not supported
从SpringMVC来分析在SpringBoot环境下出现的Jackson自动化配置,以及出现的序列化问题,给出推荐的Jackson配置
个人博客:无奈何杨(wnhyang)
个人语雀:wnhyang
共享语雀:在线知识共享
Github:wnhyang - Overview
参考
【相见欢】Spring MVC 源码剖析(五) —— 消息转换器 HttpMessageConverter | 芋道源码 —— 纯源码解析博客
Spring boot 中时间类型的序列化与反序列化 - 掘金
Failed to deserialize java.time.LocalDateTime
这是web
开发中常见的一个错误,无法完成LocalDataTime
的反序列化。
这是怎么回事?
其实这个问题在于LocalTime
、LocalDate
、LocalDateTime
的序列化上。
这就要从SpringMVC
说起了!
Spring MVC
是 Spring Framework
的核心组件之一,与其他模块(如 Spring Boot
、Spring Data
、Spring Security
等)一起构成了完整的 Spring
生态系统。
Spring MVC
是Web
开发中极其重要,其设计思想是非常值得学习的。
关于SpringMVC
的学习可以参考以下文章
Category: Spring-MVC | 芋道源码 —— 纯源码解析博客
请求报文在传输中是要进行序列化和反序列化的,有大致经历了如下流程。
其中这个HttpMessageConver
是这个过程中的关键,这个接口继承树如下图。
默认配置下Spring MVC
使用Jackson
库进行json
处理,使用MappingJackson2HttpMessageConverter
,在其中有一个非常重要的类ObjectMapper
。
从自动化配置JacksonHttpMessageConvertersConfiguration
来看,MappingJackson2HttpMessageConverter
的创建使用new MappingJackson2HttpMessageConverter(objectMapper)
方法,使用了objectMapper
的bean
。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ObjectMapper.class)
@ConditionalOnBean(ObjectMapper.class)
@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY,
havingValue = "jackson", matchIfMissing = true)
static class MappingJackson2HttpMessageConverterConfiguration {
@Bean
@ConditionalOnMissingBean(value = MappingJackson2HttpMessageConverter.class,
ignoredType = {
"org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter",
"org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter" })
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
return new MappingJackson2HttpMessageConverter(objectMapper);
}
}
关于ObjectMapper
的bean
要从JacksonAutoConfiguration
自动化配置来看,如下。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
static class JacksonObjectMapperConfiguration {
@Bean
@Primary
@ConditionalOnMissingBean
ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
return builder.createXmlMapper(false).build();
}
}
ObjectMapper
的创建由Jackson2ObjectMapperBuilder
构建而来,可以在同一个类中看到。
从以下方法可以看到,customize(builder, customizers);
此方法很关键,其使用到了Jackson2ObjectMapperBuilderCustomizer
,这个也是官方提供的,用于进一步自定义ObjectMapper
的接口。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
static class JacksonObjectMapperBuilderConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(ApplicationContext applicationContext,
List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.applicationContext(applicationContext);
customize(builder, customizers);
return builder;
}
private void customize(Jackson2ObjectMapperBuilder builder,
List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
for (Jackson2ObjectMapperBuilderCustomizer customizer : customizers) {
customizer.customize(builder);
}
}
}
Jackson2ObjectMapperBuilderCustomizer
如下。
/**
* Callback interface that can be implemented by beans wishing to further customize the
* {@link ObjectMapper} through {@link Jackson2ObjectMapperBuilder} retaining its default
* auto-configuration.
*
* @author Grzegorz Poznachowski
* @since 1.4.0
*/
@FunctionalInterface
public interface Jackson2ObjectMapperBuilderCustomizer {
/**
* Customize the JacksonObjectMapperBuilder.
* @param jacksonObjectMapperBuilder the JacksonObjectMapperBuilder to customize
*/
void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder);
}
默认SpringBoot
存在此接口的一个实现StandardJackson2ObjectMapperBuilderCustomizer
@Bean
StandardJackson2ObjectMapperBuilderCustomizer standardJacksonObjectMapperBuilderCustomizer(
ApplicationContext applicationContext, JacksonProperties jacksonProperties) {
return new StandardJackson2ObjectMapperBuilderCustomizer(applicationContext, jacksonProperties);
}
其实现此接口,方法如下,这些方法很关键,可以查看源码探究一下。
@Override
public void customize(Jackson2ObjectMapperBuilder builder) {
if (this.jacksonProperties.getDefaultPropertyInclusion() != null) {
builder.serializationInclusion(this.jacksonProperties.getDefaultPropertyInclusion());
}
if (this.jacksonProperties.getTimeZone() != null) {
builder.timeZone(this.jacksonProperties.getTimeZone());
}
configureFeatures(builder, FEATURE_DEFAULTS);
configureVisibility(builder, this.jacksonProperties.getVisibility());
configureFeatures(builder, this.jacksonProperties.getDeserialization());
configureFeatures(builder, this.jacksonProperties.getSerialization());
configureFeatures(builder, this.jacksonProperties.getMapper());
configureFeatures(builder, this.jacksonProperties.getParser());
configureFeatures(builder, this.jacksonProperties.getGenerator());
configureDateFormat(builder);
configurePropertyNamingStrategy(builder);
configureModules(builder);
configureLocale(builder);
configureDefaultLeniency(builder);
configureConstructorDetector(builder);
}
其中configureModules
方法Module
类型的bean
。
private void configureModules(Jackson2ObjectMapperBuilder builder) {
Collection<Module> moduleBeans = getBeans(this.applicationContext, Module.class);
builder.modulesToInstall(moduleBeans.toArray(new Module[0]));
}
此抽象类用于ObjectMapper
支持新的数据类型。
/**
* Simple interface for extensions that can be registered with {@link ObjectMapper}
* to provide a well-defined set of extensions to default functionality; such as
* support for new data types.
*/
public abstract class Module implements Versioned
正好在Jackson2ObjectMapperBuilder
类中注释有下面红框里这样的,只要引用这些类就可以支持其类型。
因为这些包实现一些接口,增加了新的序列化和反序列化类。
到这里几乎就能清楚了一些关于SpringMVC
出现的json序列化和反序列化的问题了。
增加需要的依赖
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type java.time.LocalTime not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling
这个错误是由于Java 8中的LocalTime类型不被默认支持导致的。要解决这个错误,需要添加JDK 8的日期/时间API依赖项到您的项目中。
在您的pom.xml文件中添加以下依赖项:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson-version}</version>
</dependency>
这里要注意,所有序列化是有默认的序列化方法的,如下。
可以看到其使用的ISO的序列化方式,简单地讲就是会有yyyy-MM-dd'T'HH:mm:ss
、yyyy-MM-dd HH:mm:ss,SSS
的差别,使用时要注意些。
虽然有@JsonFormat
这样的注解也可以完成序列化配置,但更推荐有特殊要求时使用,项目中还是统一配置一下吧。
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateTime;
推荐配置
仅供参考,此时使用了Hutool
的DatePattern
。
/**
* @author wnhyang
* @date 2024/6/20
**/
@Configuration
@Slf4j
public class JacksonConfig {
public static JavaTimeModule buildJavaTimeModule() {
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DatePattern.NORM_TIME_FORMATTER));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DatePattern.NORM_DATE_FORMATTER));
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_FORMATTER));
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DatePattern.NORM_TIME_FORMATTER));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DatePattern.NORM_DATE_FORMATTER));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DatePattern.NORM_DATETIME_FORMATTER));
return javaTimeModule;
}
@Bean
@ConditionalOnMissingBean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
log.info("[Jackson2ObjectMapperBuilderCustomizer][初始化customizer配置]");
return builder -> {
builder.locale(Locale.CHINA);
builder.timeZone(TimeZone.getTimeZone(ZoneId.systemDefault()));
builder.simpleDateFormat(DatePattern.NORM_DATETIME_PATTERN);
builder.serializerByType(Long.class, ToStringSerializer.instance);
builder.modules(buildJavaTimeModule());
};
}
}
配置说明补充
Jackson2ObjectMapperBuilderCustomizer
接口允许开发者自定义ObjectMapper
的行为,而ObjectMapper
是Jackson
库中用于JSON
序列化和反序列化的核心组件。下面分别说明在该接口实现中使用modules
方法和serializerByType
方法对ObjectMapper
配置的影响:
1、modules
方法:
- 功能:添加模块(
Module
)到ObjectMapper
中。 - 影响:通过模块可以扩展
Jackson
的功能,例如添加新的序列化器、反序列化器、类型信息处理器等。这些模块可能包含一组针对特定类型的序列化和反序列化规则,或者提供一些全局的配置选项。例如,Java 8
日期时间API
(JSR-310
)的支持就是通过JavaTimeModule
模块来实现的
builder.modules(new JavaTimeModule());
2、 serializerByType
方法:
- 功能:为特定类型注册一个自定义的序列化器。
- 影响:当遇到指定类型的对象需要进行
JSON
序列化时,会使用设置好的自定义序列化器进行处理,而不是默认的序列化方式。
builder.serializerByType(Long.class, ToStringSerializer.instance);
上述代码片段的作用是将所有Long
类型的值在序列化时转换为其字符串表示形式,而非默认的数字形式。
总结来说,modules
方法主要用于引入整个功能模块或大规模的配置更改,而serializerByType
方法则用于精确控制单个类型如何被序列化到JSON
。两者共同作用于ObjectMapper
上,以满足应用程序对于JSON数据转换的各种定制需求。
优先级
在Jackson2ObjectMapperBuilder
中,通过modules
方法添加的模块和通过serializerByType
设置的特定类型序列化器,在处理对象序列化时的优先级关系是:
- 当
ObjectMapper
遇到一个需要序列化的对象时,会首先查找是否有为该具体类型注册的自定义序列化器(如通过serializerByType
设置)。 - 若没有找到针对该类型的自定义序列化器,它会继续查找已添加的所有模块(如通过
modules
方法添加),看这些模块中是否存在对该类型的序列化规则。
因此,对于特定类型而言,如果同时通过serializerByType
设置了自定义序列化器并且在模块中有相应的序列化规则,那么serializerByType
设置的序列化器将具有更高的优先级。不过通常情况下,开发者应当避免这样的冲突设计,确保配置的清晰和一致性。
写在最后
拙作艰辛,字句心血,望诸君垂青,多予支持,不胜感激。
个人博客:无奈何杨(wnhyang)
个人语雀:wnhyang
共享语雀:在线知识共享
Github:wnhyang - Overview
更多推荐
所有评论(0)