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<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;
}
}
这个配置类实现了三种个性化配置:
设置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


猜你喜欢
- 本文实例讲述了C#迷你猜数。分享给大家供大家参考。具体如下:using System; using System.Collections.G
- 说明在学习jvm相关知识时,一般会讲到类字节码相关内容,为了更清晰的了解类字码具体内容,一般我们会使用javap命令进行查看,但是仍然不够直
- 1.android中利用webview调用网页上的js代码。Android 中可以通过webview来实现和js的交互,在程序中调用js代码
- 安卓自定义分段式的进度条,供大家参考,具体内容如下前一段时间公司新项目接到一个新需求,其中界面需要用到一个分段式的进度条,找了半天没有发现类
- JSTL JSTL简介:JSTL的全称:JSP Standard Tag Library,JSP标准标签库JSTL的作用:&nbs
- Mybatis-Plus是一个优秀的Mybatis增强工具,目前更新到3.1.1。Mybatis-Plus原生提供了很多单表操作的方法,极大
- ScrapySharp是一个帮助我们快速实现网页数据采集的库,它主要提供了如下两个功能从Url获取Html数据提供CSS选择器的方式解析Ht
- 一、何为栈?栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称
- 最近在我参与的几个.Net项目中都有用到异步编程,作为一名.Net小白,很有必要好好地学习一下C#异步编程。什么是异步异步指的就是不用阻塞当
- 本文实例为大家分享了AsyncTask异步类实现网页内容放大缩小的详细代码,供大家参考,具体内容如下WebActivity.java:pac
- 前言大家都知道在Android WebView使用中,经常需要H5页面和Native页面进行交互,比如在网页上点击分享按钮,调用本地分享接口
- Dubbo的扩展点加载从JDK标准的SPI(Service Provider Interface)扩展点发现机制加强而来。Dubbo改进了J
- 简介Java注解是JDK1.5引入的一种注释机制,它不会改变编译器的编译方式,Java编译器对包含注解和不包含注解的代码会生成相同的Java
- 本文实例所述为Android天气预报之解析天气数据的代码,可实现获取HttpGet对象读取天气网站天气数据,并从数据中解析出天气数据,比如温
- 一、Kotlin 调用 Java1. kotlin 关键字转义java 中的方法或变量 是 kotlin 的关键字时,使用反引号 `` 对关
- 使用@Autowired注解有错误提示使用Spring boot +mybatis框架时,在service实现类中使用Mapper类,给Ma
- 发送虚拟请求访问controller我们在test类中虚拟访问controller,就得发送虚拟请求。先创建一个controllerpack
- 前言以多个客户端和一个服务端的socket通信为例,服务端启动时创建一个固定大小的线程池。服务端每接收到一个连接请求后(通信任务),交给线程
- 一、前言本篇文章聚焦在“如何使用FragmentStatePagerAdapter来保存Fragment的数据、在内存中
- 实时代码模板(Live Templates)我们先来看一个gif图:大兄弟,你看清我的操作了么?这个就是实时代码模板的功能。我们来看一下怎么