个人博客:无奈何杨(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