Spring Boot2中如何优雅地个性化定制Jackson实现示例

作者:八卦程序 时间:2021-09-27 12:21:15 

概述

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

  • 使用@RequestBody来获取JSON参数并封装成实体对象;

  • 使用@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&lt;Jackson2ObjectMapperBuilderCustomizer&gt; customizers) {
     Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
     builder.applicationContext(applicationContext);
     customize(builder, customizers);
     return builder;
  }
  private void customize(Jackson2ObjectMapperBuilder builder,
        List&lt;Jackson2ObjectMapperBuilderCustomizer&gt; 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;
   }
}

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

  • 设置java.util.Date时间类的序列化以及反序列化的格式;

  • JSR 310日期时间处理;

  • 全局转化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"
}

你懂的,被拒了。

来源:https://segmentfault.com/a/1190000043498796

标签:SpringBoot2,定制,Jackson
0
投稿

猜你喜欢

  • C# 解决datagridview控件显示大量数据拖拉卡顿问题

    2022-03-21 12:52:24
  • C#使用StopWatch获取程序毫秒级执行时间的方法

    2023-12-21 02:44:06
  • springtask 的使用方法和 cron 表达式解析

    2023-06-19 20:05:56
  • Java实现宠物商店管理

    2023-09-14 09:14:13
  • Java 实战范例之线上婚纱摄影预定系统的实现

    2021-08-08 14:19:42
  • 使用Android studio3.6的java api方式调用opencv

    2023-10-10 17:16:38
  • 基于Android AIDL进程间通信接口使用介绍

    2021-12-28 05:15:22
  • spring cglib 与 jdk 动态代理

    2021-07-19 20:28:43
  • 在spring boot3中使用native image的最新方法

    2022-05-18 17:43:14
  • 在AOP中Spring生成代理类的两种方式

    2023-12-08 19:13:36
  • Android 圆角 ImageView类可设置弧度(代码简单)

    2022-09-06 13:13:36
  • C#实现的图片、string相互转换类分享

    2022-04-17 04:50:34
  • VS.net VSS时,编译报错:未能向文件“.csproj.FileListAbsolute.txt”写入命令行 对路径 的访问被拒绝。

    2021-10-01 18:29:12
  • 仅用5分钟极速入门Dubbo使用教程

    2022-08-08 12:08:55
  • JavaWeb实现文件上传下载功能实例解析

    2023-10-07 17:41:39
  • 简单探索 Java 中的惰性计算

    2023-11-16 20:25:28
  • spring boot 2.x html中引用css和js失效问题及解决方法

    2021-08-13 10:28:32
  • SpringBoot结合Redis实现序列化的方法详解

    2023-06-11 15:11:40
  • 如何将Mybatis连接到ClickHouse

    2023-11-06 02:35:51
  • 如何写好一个Spring组件的实现步骤

    2023-01-08 20:24:12
  • asp之家 软件编程 m.aspxhome.com