一、快速入门
为什么会说 Jackson 在 Spring Boot 生态中很重要?
Jackson
是 Spring Boot 生态中核心的 JSON 处理组件,贯穿于 Spring Boot 生态包括 Spring Cloud 的各个组件中。- 正因为日常可见,往往就越不重视。而实际开发中,初次使用 Spring Boot 生态组件,遇到的很多问题往往就出自
Jackson
。 - Spring Boot 生态中
Jackson
的使用和设计,采用了 Spring Boot 生态中非常常用的一种我称之为Customizer
设计模式。这种设计模式不仅仅用于Jackson
,在涉及的很多组件中都有体现,掌握了这个知识点,对于 Spring Boot 生态中很多设计实现就会非常容易理解了
常见的json类库都有哪些?
(1)Jackson
(2)Fastjson
(3)Gson
(4)Hutool JSON 工具
二、Spring Boot 中的 Jackson
基本用法
ObjectMapper
是 Jackson
最核心的、最底层的类,当我们想要使用 Jackson
处理 JSON 时,只要初始化一个 ObjectMapper
对象,例如:ObjectMapper objectMapper = new ObjectMapper()
,就可以编写各种各样的 JSON 处理逻辑了。
在实际的开发中,只是通过 new ObjectMapper()
对象,是不足以满足使用需求的。比如,在对象转换成 JSON 时,我们不希望结果中包含有空值的属性。那么就需要对 ObjectMapper
对象进行一些参数的设置,当然 ObjectMapper
也提供了丰富的自定义参数,方便用户定义自己的满足自己需求的 ObjectMapper
对象。
存在的问题
但这也带来的新的问题,就是每次在使用 Jackson
的时候都需要 new ObjectMapper()
对象,然后再把对应的参数设置一遍,就非常不方便。常见的解决方法就是会定义一个单例模式的 Utils,这样就不用每次重新创建 ObjectMapper
对象,还可以统一 ObjectMapper
中的参数配置。虽然这种方法在一定程度上解决了问题,但其实还是不够灵活,还存在几方面的问题:
- 一旦需要修改参数就需要修改代码;
- 这种方式是全局的方式,一旦设置就很难做差异化处理。
- Utils 的方式仅能统一自己的代码,第三方组件中如果也用了
Jackson
,就很难统一还是需要个性化处理
为了解决这个问题,在旧版本的 Spring Boot 中,默认的会帮我们创建一个 Bean 形式的 ObjectMapper
对象,同时利用 Spring Boot 的 Properties 机制,实现可以通过配置文件来修改 ObjectMapper
中的参数。
在 application.properties
中,修改配置是修改默认 ObjectMapper
最便捷的方式。
下面是 Jackson
配置的常规结构
spring.jackson.<category_name>.<feature_name>=true,false
以关闭 Jackson SerializationFeature.WRITE_DATES_AS_TIMESTAMPS 配置举例,Spring Boot 中对应的配置如下:
spring.jackson.serialization.write-dates-as-timestamps=false
除了上述功能类别之外,我们还可以配置属性包含:
spring.jackson.default-property-inclusion=always, non_null, non_absent, non_default, non_empty
这种方式进一步提升了 ObjectMapper
配置的灵活性,通过 Bean 注入的方式还可以实现 ObjectMapper
的统一,但是还是不足以完美解决使用 ObjectMapper
存在的问题:
- 这种方法的缺点是,我们无法自定义高级选项,如
LocalDateTime
的自定义日期格式,解决这个问题最终还是要回归到在代码中进行设置 - Spring Boot 中允许定义多个
ObjectMapper
Bean,不同的组件会根据自己的需求定义自己的ObjectMapper
Bean,这样就又变成无法全局统一ObjectMapper
对象。
使用@Primary注解
在代码开发中,很多问题和设计都会无法做到“完美”的,更多的时候必须要“平衡”和“取舍”。
在使用了 Spring Boot 的项目中,最常见的方式就是自己重新定义 ObjectMapper
Bean,在这个 Bean 中进行自己的参数设置,再利用 @Primary
注解标注这个 Bean,示例代码如下所示
使用了
@Primary
注解,所有注入ObjectMapper
Bean 的代码,包括第三方组件都只会注入@Primary
注解标注的ObjectMapper
Bean 。
@Bean
@Primary
public ObjectMapper objectMapper() {
JavaTimeModule module = new JavaTimeModule();
module.addSerializer(LOCAL_DATETIME_SERIALIZER);
return new ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.registerModule(module);
}
上面这种方式,本质就是一种“取舍”:“取”的是全局性配置,“舍”的是个性化配置
Spring Boot 中的 Customizer
在现今的 Spring Boot 中(只要不是太老的版本),Spring Boot 采用了一种我称之为 Customizer
设计模式来支持 Jackson
的配置。
Jackson2ObjectMapperBuilderCustomizer
那么 Spring Boot 是如何利用 Customizer
设计模式来扩展 Jackson
的呢?
Spring Boot 对 Jackson
的基础扩展,是 spring-boot-autoconfigure
包的 JacksonAutoConfiguration
类中完成。下面来看看JacksonAutoConfiguration
类中具体的实现代码
第一步:首先进行 StandardJackson2ObjectMapperBuilderCustomizer Bean 的注入
// org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
@EnableConfigurationProperties(JacksonProperties.class)
static class Jackson2ObjectMapperBuilderCustomizerConfiguration {
@Bean
StandardJackson2ObjectMapperBuilderCustomizer standardJacksonObjectMapperBuilderCustomizer(
JacksonProperties jacksonProperties, ObjectProvider<Module> modules) {
return new StandardJackson2ObjectMapperBuilderCustomizer(jacksonProperties, modules.stream().toList());
}
static final class StandardJackson2ObjectMapperBuilderCustomizer implements Jackson2ObjectMapperBuilderCustomizer, Ordered {
private final JacksonProperties jacksonProperties;
private final Collection<Module> modules;
StandardJackson2ObjectMapperBuilderCustomizer(JacksonProperties jacksonProperties, Collection<Module> modules) {
this.jacksonProperties = jacksonProperties;
this.modules = modules;
}
@Override
public int getOrder() {
return 0;
}
@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);
}
······
在这个过程中,会将 application.properties
中相关的配置,通过 Jackson2ObjectMapperBuilderCustomizer
方式设置进来。
这里可以注意到,StandardJackson2ObjectMapperBuilderCustomizer
不仅实现了 Jackson2ObjectMapperBuilderCustomizer
接口,还实现了 Ordered
接口。通过 Ordered
接口,来控制不同 Customizer 的配置顺序。
StandardJackson2ObjectMapperBuilderCustomizer
的 Order 被设置为 “0”,意味着其它 Customizer 的 Order 值,如果比 0 小,就在 StandardJackson2ObjectMapperBuilderCustomizer
之前配置;如果比 0 大,则在 StandardJackson2ObjectMapperBuilderCustomizer
之后配置。
第二歩:进行 Jackson2ObjectMapperBuilder
Bean 的注入
@Configuration
@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
static class JacksonObjectMapperBuilderConfiguration {
private final ApplicationContext applicationContext;
JacksonObjectMapperBuilderConfiguration(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Bean
@ConditionalOnMissingBean
public Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(
List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.applicationContext(this.applicationContext);
customize(builder, customizers);
return builder;
}
private void customize(Jackson2ObjectMapperBuilder builder,
List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
for (Jackson2ObjectMapperBuilderCustomizer customizer : customizers) {
customizer.customize(builder);
}
}
}
在这个过程中,会拿到所有的 Jackson2ObjectMapperBuilderCustomizer
类型的Bean,将其对应的配置,设置到 Jackson2ObjectMapperBuilder
中。
注意:这里如上一部分所说,会根据不同 Customizer 的 Order 顺序进行处理,这就意味着会存在相同配置的覆盖。
第三步:进行 ObjectMapper Bean 的注入
@Configuration
@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
static class JacksonObjectMapperConfiguration {
@Bean
@Primary
@ConditionalOnMissingBean
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
return builder.createXmlMapper(false).build();
}
}
这里通过已经设置好各项配置的 Jackson2ObjectMapperBuilder
,来创建 ObjectMapper
。这里使用了 @Primary
,那么所有通过注入方式获取到的 ObjectMapper
对象将会是同一个对象。
总结:
用户实现自定义定制化器编写,底层Mapperbuilder对象会在实例化对象时加载定制化器来增强属性,最终的Object Mapper通过Mapperbuilder来构建生成。