乐趣区

关于springboot:Spring-Boot2中如何优雅地个性化定制Jackson

概述

本文的编写初衷,是想理解一下 Spring Boot2 中,具体是怎么序列化和反序列化 JSR 310 日期工夫体系的,Spring MVC 利用场景有如下两个:

  1. 应用 @RequestBody 来获取 JSON 参数并封装成实体对象;
  2. 应用 @ResponseBody 来把返回给前端的数据转换成 JSON 数据。

对于一些 Integer、String 等根底类型的数据,Spring MVC 能够通过一些内置转换器来解决,无需用户关怀,然而日期工夫类型(例如 LocalDateTime),因为格局多变,没有内置转换器可用,就须要用户本人来配置和解决了。

浏览本文,假如读者初步理解了如何应用 Jackson。

测试环境

本文应用 Spring Boot2.6.6 版本,锁定的 Jackson 版本如下:

<jackson-bom.version>2.13.2.20220328</jackson-bom.version>

Jackson 解决 JSR 310 日期工夫须要引入依赖:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.13.2</version>
</dependency>

Spring Boot 主动配置

在 spring-boot-autoconfigure 包中,主动配置了 Jackson:

package org.springframework.boot.autoconfigure.jackson;

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ObjectMapper.class)
public class JacksonAutoConfiguration {// 具体代码略}

其中有一段代码配置了 ObjectMapper

@Bean
@Primary
@ConditionalOnMissingBean
ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {return builder.createXmlMapper(false).build();}

能够看到 ObjectMapper 是由 Jackson2ObjectMapperBuilder 构建的。

再往下会看到如下代码:

@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);
      }
   }

}

发现在这里创立了 Jackson2ObjectMapperBuilder,并且调用了 customize(builder, customizers) 办法,传入 Lis<Jackson2ObjectMapperBuilderCustomizer> 进行定制 ObjectMapper。

Jackson2ObjectMapperBuilderCustomizer 是个接口,只有一个办法,源码如下:

@FunctionalInterface
public interface Jackson2ObjectMapperBuilderCustomizer {

   /**
    * Customize the JacksonObjectMapperBuilder.
    * @param jacksonObjectMapperBuilder the JacksonObjectMapperBuilder to customize
    */
   void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder);

}

简略点说,Spring Boot 会收集容器外面所有的 Jackson2ObjectMapperBuilderCustomizer 实现类,对立对 Jackson2ObjectMapperBuilder 进行设置,从而实现定制 ObjectMapper。因而,如果咱们想个性化定制 ObjectMapper,只须要实现 Jackson2ObjectMapperBuilderCustomizer 接口并注册到容器就能够了。

自定义 Jackson 配置类

废话不多说,间接上代码:

@Component
public class JacksonConfig implements Jackson2ObjectMapperBuilderCustomizer, Ordered {

    /** 默认日期工夫格局 */
    private final String dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
    /** 默认日期格局 */
    private final String dateFormat = "yyyy-MM-dd";
    /** 默认工夫格局 */
    private final String timeFormat = "HH:mm:ss";

    @Override
    public void customize(Jackson2ObjectMapperBuilder builder) {
        // 设置 java.util.Date 工夫类的序列化以及反序列化的格局
        builder.simpleDateFormat(dateTimeFormat);

        // JSR 310 日期工夫解决
        JavaTimeModule javaTimeModule = new JavaTimeModule();

        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(dateTimeFormat);
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter));
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter));

        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(dateFormat);
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(dateFormatter));
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(dateFormatter));

        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern(timeFormat);
        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(timeFormatter));
        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(timeFormatter));

        builder.modules(javaTimeModule);

        // 全局转化 Long 类型为 String,解决序列化后传入前端 Long 类型精度失落问题
        builder.serializerByType(BigInteger.class, ToStringSerializer.instance);
        builder.serializerByType(Long.class,ToStringSerializer.instance);
    }

    @Override
    public int getOrder() {return 1;}
}

这个配置类实现了三种个性化配置:

  1. 设置 java.util.Date 工夫类的序列化以及反序列化的格局;
  2. JSR 310 日期工夫解决;
  3. 全局转化 Long 类型为 String,解决序列化后传入前端 Long 类型缺失精度问题。

当然,读者还能够按本人的需要持续进行定制其余配置。

测试

这里用 JSR 310 日期工夫进行测试。

创立实体类 User

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Long id;
    private String name;
    private LocalDate localDate;
    private LocalTime localTime;
    private LocalDateTime localDateTime;
}

创立控制器 UserController

@RestController
@RequestMapping("user")
public class UserController {@PostMapping("test")
    public User test(@RequestBody User user){System.out.println(user.toString());
        return user;
    }

}

前端传参

{
  "id": 184309536616640512,
  "name": "八卦程序",
  "localDate": "2023-03-01",
  "localTime": "09:35:50",
  "localDateTime": "2023-03-01 09:35:50"
}

后端返回数据

{
  "id": "184309536616640512",
  "name": "八卦程序",
  "localDate": "2023-03-01",
  "localTime": "09:35:50",
  "localDateTime": "2023-03-01 09:35:50"
}

能够看到,前端传入了什么数据,后端就返回了什么数据,惟一的区别就是后端返回的 id 是字符串了,能够避免前端(例如 JavaScript)呈现精度失落问题。

同时也证实 LocalDateTime 等日期工夫类型,到后端参观了一圈,又失常返回了(没有被拒,也没有受到后端毒打变形,例如变成工夫戳回来,导致亲妈都不意识了)。

前端表白被拒

如果不配置 JacksonConfig 呢,Spring MVC 在尝试内置转换器无果后,会报异样如下:
JSON parse error: Cannot deserialize value of type java.time.LocalDateTime

返回给前端的数据如下:

{
  "timestamp": "2023-03-01T09:53:02.158+00:00",
  "status": 400,
  "error": "Bad Request",
  "path": "/user/test"
}

你懂的,被拒了。

总结

外围类 ObjectMapper

ObjectMapper 是 jackson-databind 模块最为重要的一个类,它实现了数据处理的简直所有性能。
只管 Spring MVC 在解决前端传递的 JSON 参数时,进行了一系列目迷五色的操作,然而一顿操作猛如虎,最终还是靠 ObjectMapper 来实现序列化和反序列化。因而,只须要对 Spring Boot 默认提供的 ObjectMapper 进行个性化定制即可。

不要笼罩默认配置

咱们通过实现 Jackson2ObjectMapperBuilderCustomizer 接口并注册到容器,进行个性化定制,Spring Boot 不会笼罩默认 ObjectMapper 的配置,而是进行了合并加强,具体还会依据 Jackson2ObjectMapperBuilderCustomizer 实现类的 Order 优先级进行排序,因而下面的 JacksonConfig 配置类还实现了 Ordered 接口。

默认的 Jackson2ObjectMapperBuilderCustomizerConfiguration 优先级是 0,因而如果咱们想要笼罩配置,设置优先级大于 0 即可。

留神:在 SpringBoot2 环境下,不要将自定义的 ObjectMapper 对象注入容器,这样会将原有的 ObjectMapper 配置笼罩!

QueryString 格局参数

须要留神的是,Jackson 不能解决 QueryString 格局参数的问题,因为 Spring 对于这类参数用的是 Converter 类型转换机制,那就是另一条参数绑定之路了(不好意思,Jackson 没在这条路上帮忙)。
须要自定义参数类型转换器来解决日期工夫类型,须要另写文章介绍了。

集体网站,点击围观:八卦程序

退出移动版