Spring Boot2中如何優(yōu)雅地個性化定制Jackson實現(xiàn)示例
概述
本文的編寫初衷,是想了解一下Spring Boot2中,具體是怎么序列化和反序列化JSR 310日期時間體系的,Spring MVC應(yīng)用場景有如下兩個:
- 使用@RequestBody來獲取JSON參數(shù)并封裝成實體對象;
- 使用@ResponseBody來把返回給前端的數(shù)據(jù)轉(zhuǎn)換成JSON數(shù)據(jù)。
對于一些Integer、String等基礎(chǔ)類型的數(shù)據(jù),Spring MVC可以通過一些內(nèi)置轉(zhuǎn)換器來解決,無需用戶關(guān)心,但是日期時間類型(例如LocalDateTime),由于格式多變,沒有內(nèi)置轉(zhuǎn)換器可用,就需要用戶自己來配置和處理了。
閱讀本文,假設(shè)讀者初步了解了如何使用Jackson。
測試環(huán)境
本文使用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 { // 詳細(xì)代碼略 }
其中有一段代碼配置了ObjectMapper
@Bean @Primary @ConditionalOnMissingBean ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { return builder.createXmlMapper(false).build(); }
可以看到ObjectMapper是由Jackson2ObjectMapperBuilder構(gòu)建的。
再往下會看到如下代碼:
@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); } } }
發(fā)現(xiàn)在這里創(chuàng)建了Jackson2ObjectMapperBuilder,并且調(diào)用了customize(builder, customizers)方法,傳入Lis<Jackson2ObjectMapperBuilderCustomizer> 進(jìn)行定制ObjectMapper。
Jackson2ObjectMapperBuilderCustomizer是個接口,只有一個方法,源碼如下:
@FunctionalInterface public interface Jackson2ObjectMapperBuilderCustomizer { /** * Customize the JacksonObjectMapperBuilder. * @param jacksonObjectMapperBuilder the JacksonObjectMapperBuilder to customize */ void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder); }
簡單點說,Spring Boot會收集容器里面所有的Jackson2ObjectMapperBuilderCustomizer實現(xiàn)類,統(tǒng)一對Jackson2ObjectMapperBuilder進(jìn)行設(shè)置,從而實現(xiàn)定制ObjectMapper。因此,如果我們想個性化定制ObjectMapper,只需要實現(xiàn)Jackson2ObjectMapperBuilderCustomizer接口并注冊到容器就可以了。
自定義Jackson配置類
廢話不多說,直接上代碼:
@Component public class JacksonConfig implements Jackson2ObjectMapperBuilderCustomizer, Ordered { /** 默認(rèn)日期時間格式 */ private final String dateTimeFormat = "yyyy-MM-dd HH:mm:ss"; /** 默認(rèn)日期格式 */ private final String dateFormat = "yyyy-MM-dd"; /** 默認(rèn)時間格式 */ private final String timeFormat = "HH:mm:ss"; @Override public void customize(Jackson2ObjectMapperBuilder builder) { // 設(shè)置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); // 全局轉(zhuǎn)化Long類型為String,解決序列化后傳入前端Long類型精度丟失問題 builder.serializerByType(BigInteger.class, ToStringSerializer.instance); builder.serializerByType(Long.class,ToStringSerializer.instance); } @Override public int getOrder() { return 1; } }
這個配置類實現(xiàn)了三種個性化配置:
- 設(shè)置java.util.Date時間類的序列化以及反序列化的格式;
- JSR 310日期時間處理;
- 全局轉(zhuǎn)化Long類型為String,解決序列化后傳入前端Long類型缺失精度問題。
當(dāng)然,讀者還可以按自己的需求繼續(xù)進(jìn)行定制其他配置。
測試
這里用JSR 310日期時間進(jìn)行測試。
創(chuàng)建實體類User
@Data @NoArgsConstructor @AllArgsConstructor public class User { private Long id; private String name; private LocalDate localDate; private LocalTime localTime; private LocalDateTime localDateTime; }
創(chuàng)建控制器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" }
后端返回數(shù)據(jù)
{ "id": "184309536616640512", "name": "八卦程序", "localDate": "2023-03-01", "localTime": "09:35:50", "localDateTime": "2023-03-01 09:35:50" }
可以看到,前端傳入了什么數(shù)據(jù),后端就返回了什么數(shù)據(jù),唯一的區(qū)別就是后端返回的id是字符串了,可以防止前端(例如JavaScript)出現(xiàn)精度丟失問題。
同時也證明LocalDateTime等日期時間類型,到后端參觀了一圈,又正常返回了(沒有被拒,也沒有遭到后端毒打變形,例如變成時間戳回來,導(dǎo)致親媽都不認(rèn)識了)。
前端表白被拒
如果不配置JacksonConfig呢,Spring MVC在嘗試內(nèi)置轉(zhuǎn)換器無果后,會報異常如下:
JSON parse error: Cannot deserialize value of type java.time.LocalDateTime
返回給前端的數(shù)據(jù)如下:
{ "timestamp": "2023-03-01T09:53:02.158+00:00", "status": 400, "error": "Bad Request", "path": "/user/test" }
你懂的,被拒了。
總結(jié)
核心類ObjectMapper
ObjectMapper是jackson-databind模塊最為重要的一個類,它完成了數(shù)據(jù)處理的幾乎所有功能。
盡管Spring MVC在處理前端傳遞的JSON參數(shù)時,進(jìn)行了一系列眼花繚亂的操作,但是一頓操作猛如虎,最終還是靠ObjectMapper來完成序列化和反序列化。因此,只需要對Spring Boot默認(rèn)提供的ObjectMapper進(jìn)行個性化定制即可。
不要覆蓋默認(rèn)配置
我們通過實現(xiàn)Jackson2ObjectMapperBuilderCustomizer接口并注冊到容器,進(jìn)行個性化定制,Spring Boot不會覆蓋默認(rèn)ObjectMapper的配置,而是進(jìn)行了合并增強,具體還會根據(jù)Jackson2ObjectMapperBuilderCustomizer實現(xiàn)類的Order優(yōu)先級進(jìn)行排序,因此上面的JacksonConfig配置類還實現(xiàn)了Ordered接口。
默認(rèn)的Jackson2ObjectMapperBuilderCustomizerConfiguration優(yōu)先級是0,因此如果我們想要覆蓋配置,設(shè)置優(yōu)先級大于0即可。
注意:在SpringBoot2環(huán)境下,不要將自定義的ObjectMapper對象注入容器,這樣會將原有的ObjectMapper配置覆蓋!
QueryString格式參數(shù)
需要注意的是,Jackson不能解決QueryString格式參數(shù)的問題,因為Spring對于這類參數(shù)用的是Converter類型轉(zhuǎn)換機制,那就是另一條參數(shù)綁定之路了(不好意思,Jackson沒在這條路上幫忙)。
需要自定義參數(shù)類型轉(zhuǎn)換器來處理日期時間類型,需要另寫文章介紹了,更多關(guān)于Spring Boot2定制Jackson的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot自定義線程池,執(zhí)行定時任務(wù)方式
這篇文章主要介紹了SpringBoot自定義線程池,執(zhí)行定時任務(wù)方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-04-04SpringBoot集成Prometheus實現(xiàn)監(jiān)控的過程
這篇文章主要介紹了SpringBoot集成Prometheus實現(xiàn)監(jiān)控,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-09-09淺談resultMap的用法及關(guān)聯(lián)結(jié)果集映射
這篇文章主要介紹了resultMap的用法及關(guān)聯(lián)結(jié)果集映射操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06