概述
本文的撰寫初衷,是想了解一下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"
}
你懂的,被拒了,
總結
核心類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沒在這條路上幫忙),
需要自定義引數型別轉換器來處理日期時間型別,需要另寫文章介紹了,
個人網站,點擊圍觀:八卦程式

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/545686.html
標籤:Java
下一篇:Redis分布式鎖常見坑點分析
