个人博客:无奈何杨(wnhyang)
个人语雀:wnhyang
共享语雀:在线知识共享
Github:wnhyang -
Overview
参考
【相见欢】Spring
MVC 源码剖析(五) —— 消息转换器 HttpMessageConverter | 芋道源码 ——
纯源码解析博客
Spring boot
中时间类型的序列化与反序列化 - 掘金
Spring boot 中
Jackson 的常用配置
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 | 芋道源码 —— 纯源码解析博客
请求报文在传输中是要进行序列化和反序列化的,有大致经历了如下流程。
image
其中这个HttpMessageConver
是这个过程中的关键,这个接口继承树如下图。
image
默认配置下Spring MVC
使用Jackson
库进行json
处理,使用MappingJackson2HttpMessageConverter
,在其中有一个非常重要的类ObjectMapper
。
image
从自动化配置JacksonHttpMessageConvertersConfiguration
来看,MappingJackson2HttpMessageConverter
的创建使用new MappingJackson2HttpMessageConverter(objectMapper)
方法,使用了objectMapper
的bean
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @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
自动化配置来看,如下。
1 2 3 4 5 6 7 8 9 10 11 12 @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
的接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @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
如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @FunctionalInterface public interface Jackson2ObjectMapperBuilderCustomizer { void customize (Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) ; }
默认SpringBoot
存在此接口的一个实现StandardJackson2ObjectMapperBuilderCustomizer
1 2 3 4 5 @Bean StandardJackson2ObjectMapperBuilderCustomizer standardJacksonObjectMapperBuilderCustomizer ( ApplicationContext applicationContext, JacksonProperties jacksonProperties) { return new StandardJackson2ObjectMapperBuilderCustomizer (applicationContext, jacksonProperties); }
其实现此接口,方法如下,这些方法很关键,可以查看源码探究一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @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
。
1 2 3 4 private void configureModules (Jackson2ObjectMapperBuilder builder) { Collection<Module> moduleBeans = getBeans(this .applicationContext, Module.class); builder.modulesToInstall(moduleBeans.toArray(new Module [0 ])); }
此抽象类用于ObjectMapper
支持新的数据类型。
1 2 3 4 5 6 public abstract class Module implements Versioned
正好在Jackson2ObjectMapperBuilder
类中注释有下面红框里这样的,只要引用这些类就可以支持其类型。
image
因为这些包实现一些接口,增加了新的序列化和反序列化类。
image
到这里几乎就能清楚了一些关于SpringMVC
出现的json序列化和反序列化的问题了。
增加需要的依赖
1 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文件中添加以下依赖项:
1 2 3 4 5 <dependency > <groupId > com.fasterxml.jackson.datatype</groupId > <artifactId > jackson-datatype-jsr310</artifactId > <version > ${jackson-version}</version > </dependency >
这里要注意,所有序列化是有默认的序列化方法的,如下。
image
可以看到其使用的ISO的序列化方式,简单地讲就是会有yyyy-MM-dd'T'HH:mm:ss
、yyyy-MM-dd HH:mm:ss,SSS
的差别,使用时要注意些。
image
虽然有@JsonFormat
这样的注解也可以完成序列化配置,但更推荐有特殊要求时使用,项目中还是统一配置一下吧。
1 2 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime dateTime;
推荐配置
仅供参考,此时使用了Hutool
的DatePattern
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @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
模块来实现的
1 builder.modules(new JavaTimeModule ());
2、 serializerByType
方法:
功能:为特定类型注册一个自定义的序列化器。
影响:当遇到指定类型的对象需要进行JSON
序列化时,会使用设置好的自定义序列化器进行处理,而不是默认的序列化方式。
1 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