背景

1.1 问题

Spring Boot 在处理对象的序列化和反序列化时,默认使用框架自带的JackSon配置。使用框架默认的,通常会面临如下问题:
  1. Date返回日期格式(建议不使用Date,但老项目要兼容),带有T,如 2018-05-15T24:59:59:
  1. LocalDate返回日期对象为数组(框架中继承了 WebMvcConfigurationSupport);
  1. LocalDateTime时间转换失败等;
  1. 定义了日期类型,如LocalDate,前端对接时(post/get),如果传入日期字符串("2022-05-05"),会报String 转换为LocalDate失败;
  1. 返回long型数据,前端js存在精度问题,需做转换;
  1. 一些特殊对象要做业务特殊转换,如加解密等;

1.2 解决方案

针对上述问题,存在很多种解决方案。由于底层框架统一配置拦截类实现的模式不同,还是会存在差异,本文主要说明在不同的配置场景下,自定义Jackson配置的一些注意事项和差异化原因:
为了解决特殊对象(如日期)的序列化和反序列化问题,常用方案如下:
  1. 针对特殊的具体对象,在对象上面使用注解,如:
@JsonSerialize(using= JsonDateSerializer.class)
private Date taskEndTime; @ApiModelProperty(value = "检查日期")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate checkDate;
  1. 重新实现WebMvcConfigurer接口,自定义JackSon配置。
  1. 继承 WebMvcConfigurationSupport类,自定义JackSon配置。

1.3 特别说明

  • 方案1的模式,在对应变量上加上注解,是可以解决问题,但是严重编码重复,不优雅;
  • 实现WebMvcConfigurer接口与继承WebMvcConfigurationSupport类,是Spring Boot提供开发者做统全局配置类的两种模式,注意两种模式的差异,详情查看后续章节介绍(两种不同的模式,使用不当时,就会出现配置不生效的情况);

自定义Jackson

  1. JackSon配置说明

自定义一个Jackson配置信息,需要了解Jackson的一些配置标准,如:

//在反序列化时忽略在 json 中存在但 Java 对象不存在的属性

mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,

false);

//在序列化时日期格式默认为 yyyy-MM-dd'T'HH:mm:ss.SSSZ ,比如如果一个类中有private Date date;这种日期属性,序列化后为:{"date" : 1413800730456},若不为true,则为{"date" : "2014-10-20T10:26:06.604+0000"}

mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,false);

//在序列化时忽略值为 null 的属性

mapper.setSerializationInclusion(Include.NON_NULL);

//忽略值为默认值的属性

mapper.setDefaultPropertyInclusion(Include.NON_DEFAULT);

// 美化输出

mapper.enable(SerializationFeature.INDENT_OUTPUT);

// 允许序列化空的POJO类

// (否则会抛出异常)

mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);

// 把java.util.Date, Calendar输出为数字(时间戳)

mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

// 在遇到未知属性的时候不抛出异常

mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

// 强制JSON 空字符串("")转换为null对象值:

mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);

// 在JSON中允许C/C++ 样式的注释(非标准,默认禁用)

mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);

// 允许没有引号的字段名(非标准)

mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);

// 允许单引号(非标准)

mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);

// 强制转义非ASCII字符

mapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);

// 将内容包裹为一个JSON属性,属性名由@JsonRootName注解指定

mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);

//序列化枚举是以toString()来输出,默认false,即默认以name()来输出

mapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING,true);

//序列化Map时对key进行排序操作,默认false

mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS,true);

//序列化char[]时以json数组输出,默认false

mapper.configure(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS,true);

//序列化BigDecimal时之间输出原始数字还是科学计数,默认false,即是否以toPlainString()科学计数方式来输出

mapper.configure(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS,true);
  1. 实现WebMvcConfigurer接口

重新编写一个ObjectMapper,替换系统默认的bean,就可以实现接口在post请求模式时,对象序列化与反序列化走子定义配置信息了。
重新编写Jackson后,并不能处理get请求时,日期等特殊对象的序列化处理;针对get请求,编写对象的序列化规则函数,通过实现addFormatters()接口,可扩展支持;

编写LocalDateTime转换函数

/**
* java 8 LocalDateTime转换器
*
* @author wangling
*/
public class LocalDateTimeFormatter implements Formatter<LocalDateTime> {
private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); @Override
public LocalDateTime parse(String text, Locale locale) throws ParseException {
return LocalDateTime.parse(text, formatter);
} @Override
public String print(LocalDateTime object, Locale locale) {
return formatter.format(object);
}
}

编写LocalDate转换函数

/**
* java 8 localDate转换器
*
* @author wangling
*/
public class LocalDateFormatter implements Formatter<LocalDate> {
private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); @Override
public LocalDate parse(String text, Locale locale) throws ParseException {
return LocalDate.parse(text, formatter);
} @Override
public String print(LocalDate object, Locale locale) {
return formatter.format(object);
}
}

编写Jackson配置

编写一个自定义的ObjectMapper bean对象,设置优先级替换默认bean。
/**
* 项目全局配置类
*
* @author wangling
* @date 2022/06/10
*/
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer { @Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatterForFieldType(LocalDate.class, new LocalDateFormatter());
registry.addFormatterForFieldType(LocalDateTime.class, new LocalDateTimeFormatter());
} @Bean
@Primary
public ObjectMapper ObjectMapper() {
String dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
String dateFormat = "yyyy-MM-dd";
String timeFormat = "HH:mm:ss";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
JavaTimeModule javaTimeModule = new JavaTimeModule();
// 序列化
javaTimeModule.addSerializer(
LocalDateTime.class,
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
javaTimeModule.addSerializer(
LocalDate.class,
new LocalDateSerializer(DateTimeFormatter.ofPattern(dateFormat)));
javaTimeModule.addSerializer(
LocalTime.class,
new LocalTimeSerializer(DateTimeFormatter.ofPattern(timeFormat)));
javaTimeModule.addSerializer(
Date.class,
new DateSerializer(false, new SimpleDateFormat(dateTimeFormat))); // 反序列化
javaTimeModule.addDeserializer(
LocalDateTime.class,
new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
javaTimeModule.addDeserializer(
LocalDate.class,
new LocalDateDeserializer(DateTimeFormatter.ofPattern(dateFormat)));
javaTimeModule.addDeserializer(
LocalTime.class,
new LocalTimeDeserializer(DateTimeFormatter.ofPattern(timeFormat)));
javaTimeModule.addDeserializer(Date.class, new DateDeserializers.DateDeserializer() {
@SneakyThrows
@Override
public Date deserialize(JsonParser jsonParser, DeserializationContext dc) {
String text = jsonParser.getText().trim();
SimpleDateFormat sdf = new SimpleDateFormat(dateTimeFormat);
return sdf.parse(text);
}
});
javaTimeModule.addSerializer(Long.class, ToStringSerializer.instance);
javaTimeModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}
}
  1. WebMvcConfigurationSupport类

编写Jackson配置

重新编写Jackson后,并不能处理get请求时,日期等特殊对象的序列化处理;针对get请求,编写对象的序列化规则函数,通过实现addFormatters()接口,可扩展支持;
编写自定义配置Jackson信息时,需要重写extendMessageConverters方法。具体技术细节原因,请参考文档《Spring Boot实现WebMvcConfigurationSupport导致自定义的JSON时间返回格式不生效》

/**
* 项目全局配置类
*
* @author wangling
* @date 2022/06/10
*/
@Configuration
public class MvcInterceptorConfig extends WebMvcConfigurationSupport { @Override
protected void addFormatters(FormatterRegistry registry) {
// 用于get 全局格式化日期转换
registry.addFormatterForFieldType(LocalDate.class, new LocalDateFormatter());
registry.addFormatterForFieldType(LocalDateTime.class, new LocalDateTimeFormatter());
} @Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// 代替框架默认的JackSon配置 用于post 全局格式化日期转换,long转字符串
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter =
new MappingJackson2HttpMessageConverter();
jackson2HttpMessageConverter.setObjectMapper(ObjectMapper());
// 基于顺序,先执行自定义的
converters.add(0, jackson2HttpMessageConverter);
} private ObjectMapper ObjectMapper() {
String dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
String dateFormat = "yyyy-MM-dd";
String timeFormat = "HH:mm:ss";
ObjectMapper objectMapper = new ObjectMapper();
//忽略空Bean转json的错误
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
//忽略 在json字符串中存在,但是在对象中不存在对应属性的情况,防止错误。
// 例如json数据中多出字段,而对象中没有此字段。如果设置true,抛出异常,因为字段不对应;false则忽略多出的字段,默认值为null,将其他字段反序列化成功
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
JavaTimeModule javaTimeModule = new JavaTimeModule();
// 序列化
javaTimeModule.addSerializer(
LocalDateTime.class,
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
javaTimeModule.addSerializer(
LocalDate.class,
new LocalDateSerializer(DateTimeFormatter.ofPattern(dateFormat)));
javaTimeModule.addSerializer(
LocalTime.class,
new LocalTimeSerializer(DateTimeFormatter.ofPattern(timeFormat)));
javaTimeModule.addSerializer(
Date.class,
new DateSerializer(false, new SimpleDateFormat(dateTimeFormat))); // 反序列化
javaTimeModule.addDeserializer(
LocalDateTime.class,
new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
javaTimeModule.addDeserializer(
LocalDate.class,
new LocalDateDeserializer(DateTimeFormatter.ofPattern(dateFormat)));
javaTimeModule.addDeserializer(
LocalTime.class,
new LocalTimeDeserializer(DateTimeFormatter.ofPattern(timeFormat)));
javaTimeModule.addDeserializer(Date.class, new DateDeserializers.DateDeserializer() {
@SneakyThrows
@Override
public Date deserialize(JsonParser jsonParser, DeserializationContext dc) {
String text = jsonParser.getText().trim();
SimpleDateFormat sdf = new SimpleDateFormat(dateTimeFormat);
return sdf.parse(text);
}
});
javaTimeModule.addSerializer(Long.class, ToStringSerializer.instance);
javaTimeModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}
}

WebMvcConfigurer与WebMvcConfigurationSupport相关知识点

  1. 基础知识点

Spring的 WebMvcConfigurer 接口提供了很多方法让开发者来定制SpringMVC的配置。
WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware。支持的自定义的配置更多更全,WebMvcConfigurerAdapter有的方法,这个类也都有。该类注释内容翻译:这是提供MVC Java config 背后配置的主要类。 通常是通过将@EnableWebMvc添加到应用程序的@Configuration类中来导入的。 另一个更高级的选择是直接从此类扩展并在需要时重写方法,记住子类要添加@Configuration,重写带有@Bean的方法也要加上@Bean。
  1. 使用注意事项

          参考文档:《拦截失效原因》
  1. 实现WebMvcConfigurer: 不会覆盖WebMvcAutoConfiguration的配置
  1. 实现WebMvcConfigurer+注解@EnableWebMvc:会覆盖WebMvcAutoConfiguration的配置
  1. 继承WebMvcConfigurationSupport:会覆盖WebMvcAutoConfiguration的配置
  1. 继承DelegatingWebMvcConfiguration:会覆盖WebMvcAutoConfiguration的配置
  1. 推荐使用模式

  1. 非必要,最好避免WebMvcConfigurer,WebMvcConfigurationSupport在一个项目中同时使用;
  1. 出于安全性拦截配置,建议项目采用WebMvcConfigurer接口的方式做全局配置;
  1. 日期,时间等建议使用LocalDate,替换历史的Date数据类型;
 

一文详解JackSon配置信息的更多相关文章

  1. 一文详解Hexo+Github小白建站

    作者:玩世不恭的Coder时间:2020-03-08说明:本文为原创文章,未经允许不可转载,转载前请联系作者 一文详解Hexo+Github小白建站 前言 GitHub是一个面向开源及私有软件项目的托 ...

  2. log4j.properties 详解与配置步骤(转)

    找的文章,供参考使用 转自 log4j.properties 详解与配置步骤 一.log4j.properties 的使用详解 1.输出级别的种类 ERROR.WARN.INFO.DEBUGERROR ...

  3. C3P0连接池详解及配置

    C3P0连接池详解及配置 本人使用的C3P0的jar包是:c3p0-0.9.1.jar <bean id = "dataSource" class = "com.m ...

  4. rsync的介绍及参数详解,配置步骤,工作模式介绍

    rsync的介绍及参数详解,配置步骤,工作模式介绍 rsync是类unix系统下的数据镜像备份工具.它是快速增量备份.全量备份工具. Sync可以远程同步,支持本地复制,或者与其他SSH.rsync主 ...

  5. 磁盘分区对齐详解与配置 – Linux篇

    在之前一篇<磁盘分区对齐详解与配置 – Windows篇>中,我介绍了磁盘分区对齐的作用和适用于MBR和GPT的两种磁盘类型的配置,以及Windows平台设置磁盘分区对齐的方法. 本文作为 ...

  6. tomcat启动nio,apr详解以及配置

    tomcat启动nio,apr详解以及配置 前言 在正文开始之前,我们先在idea工具中看看启动的信息,顺便看下启动的基本信息 在这里插入图片描述可以看到信息有tomcat版本操作系统版本java版本 ...

  7. 一文详解 Linux 系统常用监控工一文详解 Linux 系统常用监控工具(top,htop,iotop,iftop)具(top,htop,iotop,iftop)

    一文详解 Linux 系统常用监控工具(top,htop,iotop,iftop)     概 述 本文主要记录一下 Linux 系统上一些常用的系统监控工具,非常好用.正所谓磨刀不误砍柴工,花点时间 ...

  8. nginx的gzip模块详解以及配置

    文章来源 运维公会:nginx的gzip模块详解以及配置   1.gzip模块作用 gzip这个模块无论在测试环境还是生产环境都是必须要开启,这个模块能高效的将页面的内容,无论是html或者css.j ...

  9. SpringBoot Profile使用详解及配置源码解析

    在实践的过程中我们经常会遇到不同的环境需要不同配置文件的情况,如果每换一个环境重新修改配置文件或重新打包一次会比较麻烦,Spring Boot为此提供了Profile配置来解决此问题. Profile ...

随机推荐

  1. Vue基础二之全局API、实例属性和全局配置,以及组件进阶(mixins)的详细教程(案列实现,详细图解,附源码)

    本篇文章主要是写Vue.directive().Vue.use()等常用全局API的使用,vm.$props.vm.$options.vm.$slots等实例属性的使用,以及Vue全局配置.组件的mi ...

  2. ElasticSearch7.3学习(二十一)----Filter与Query对比、使用explain关键字分析语法

    1.数据准备 首先创建book索引 PUT /book/ { "settings": { "number_of_shards": 1, "number ...

  3. javaScript深拷贝和浅拷贝简单梳理

    在了解深拷贝和浅拷贝之前,我们先梳理一下: JavaScript中,分为基本数据类型(原始值)和复杂类型(对象),同时它们各自的数据类型细分下又有好几种数据类型 基本数据类型 数字Number 字符串 ...

  4. 变量命名 函数命名 方法 Naming cheatsheet

    Naming things is hard. This sheet attempts to make it easier. Although these suggestions can be appl ...

  5. Oracle 常用运维命令整理

    点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 一.oracle建库与删库命令 (1)oracle11g ...

  6. 【Azure Developer】使用 Microsoft Authentication Libraries (MSAL) 如何来获取Token呢 (通过用户名和密码方式获取Access Token)

    问题描述 在上一篇博文<[Azure Developer]使用 adal4j(Azure Active Directory authentication library for Java)如何来 ...

  7. css修改文子背景浮动

    伪元素选择器 """通过css操作文本内容""" 1.修改首个字体样式 p:first-letter{ color: blue; font- ...

  8. 2022年5月11日,NBMiner发布了41.3版本,在内核中加入了100%LHR解锁器,从此NVIDIA的显卡再无锁卡一说

           2022年5月11日,NBMiner发布NBMiner_41.3版本,主要提升了稳定性.         2022年5月8日,NBMiner发布NBMiner_41.0版本,在最新的内核 ...

  9. Springboot启动类及注解说明

    Spring boot的启动是基于main方法的,其主要注解为: 1. @springBootApplication:项目的启动注解,是一个组合注解,包含@SpringbootConfiguratio ...

  10. 什么是请求参数、表单参数、url参数、header参数、Cookie参数?一文讲懂

    最近在工作中对 http 的请求参数解析有了进一步的认识,写个小短文记录一下. 回顾下自己的情况,大概就是:有点点网络及编程基础,只需要加深一点点对 HTTP 协议的理解就能弄明白了. 先分享一个小故 ...