Jackson 配置与扩展

一、快速入门

为什么会说 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

基本用法

ObjectMapperJackson 最核心的、最底层的类,当我们想要使用 Jackson 处理 JSON 时,只要初始化一个 ObjectMapper 对象,例如:ObjectMapper objectMapper = new ObjectMapper(),就可以编写各种各样的 JSON 处理逻辑了。

在实际的开发中,只是通过 new ObjectMapper() 对象,是不足以满足使用需求的。比如,在对象转换成 JSON 时,我们不希望结果中包含有空值的属性。那么就需要对 ObjectMapper 对象进行一些参数的设置,当然 ObjectMapper 也提供了丰富的自定义参数,方便用户定义自己的满足自己需求的 ObjectMapper 对象。

存在的问题

但这也带来的新的问题,就是每次在使用 Jackson 的时候都需要 new ObjectMapper() 对象,然后再把对应的参数设置一遍,就非常不方便。常见的解决方法就是会定义一个单例模式的 Utils,这样就不用每次重新创建 ObjectMapper 对象,还可以统一 ObjectMapper 中的参数配置。虽然这种方法在一定程度上解决了问题,但其实还是不够灵活,还存在几方面的问题:

  1. 一旦需要修改参数就需要修改代码;
  2. 这种方式是全局的方式,一旦设置就很难做差异化处理。
  3. 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 存在的问题:

  1. 这种方法的缺点是,我们无法自定义高级选项,如 LocalDateTime 的自定义日期格式,解决这个问题最终还是要回归到在代码中进行设置
  2. 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来构建生成。

参考:   https://www.herodotus.vip/guide/design/jackson.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值