一文詳解JackSon配置信息
- 2022 年 6 月 24 日
- 筆記
- jackson, LocalDate, Spring Boot
背景
1.1 問題
Spring Boot 在處理對象的序列化和反序列化時,默認使用框架自帶的JackSon配置。使用框架默認的,通常會面臨如下問題:
- Date返回日期格式(建議不使用Date,但老項目要兼容),帶有T,如 2018-05-15T24:59:59:
- LocalDate返回日期對象為數組(框架中繼承了 WebMvcConfigurationSupport);
- LocalDateTime時間轉換失敗等;
- 定義了日期類型,如LocalDate,前端對接時(post/get),如果傳入日期字符串(”2022-05-05″),會報String 轉換為LocalDate失敗;
- 返回long型數據,前端js存在精度問題,需做轉換;
- 一些特殊對象要做業務特殊轉換,如加解密等;
1.2 解決方案
針對上述問題,存在很多種解決方案。由於底層框架統一配置攔截類實現的模式不同,還是會存在差異,本文主要說明在不同的配置場景下,自定義Jackson配置的一些注意事項和差異化原因:
為了解決特殊對象(如日期)的序列化和反序列化問題,常用方案如下:
- 針對特殊的具體對象,在對象上面使用註解,如:
@JsonSerialize(using= JsonDateSerializer.class) private Date taskEndTime; @ApiModelProperty(value = "檢查日期") @JsonFormat(pattern = "yyyy-MM-dd") private LocalDate checkDate;
- 重新實現WebMvcConfigurer接口,自定義JackSon配置。
- 繼承 WebMvcConfigurationSupport類,自定義JackSon配置。
1.3 特別說明
- 方案1的模式,在對應變量上加上註解,是可以解決問題,但是嚴重編碼重複,不優雅;
- 實現WebMvcConfigurer接口與繼承WebMvcConfigurationSupport類,是Spring Boot提供開發者做統全局配置類的兩種模式,注意兩種模式的差異,詳情查看後續章節介紹(兩種不同的模式,使用不當時,就會出現配置不生效的情況);
自定義Jackson
-
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);
View Code
-
實現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; } }
-
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; } }
View Code
WebMvcConfigurer與WebMvcConfigurationSupport相關知識點
-
基礎知識點
Spring的
WebMvcConfigurer
接口提供了很多方法讓開發者來定製SpringMVC的配置。WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware。支持的自定義的配置更多更全,WebMvcConfigurerAdapter有的方法,這個類也都有。該類注釋內容翻譯:這是提供MVC Java config 背後配置的主要類。 通常是通過將@EnableWebMvc添加到應用程序的@Configuration類中來導入的。 另一個更高級的選擇是直接從此類擴展並在需要時重寫方法,記住子類要添加@Configuration,重寫帶有@Bean的方法也要加上@Bean。
-
使用注意事項
參考文檔:《攔截失效原因》
- 實現WebMvcConfigurer: 不會覆蓋WebMvcAutoConfiguration的配置
- 實現WebMvcConfigurer+註解@EnableWebMvc:會覆蓋WebMvcAutoConfiguration的配置
- 繼承WebMvcConfigurationSupport:會覆蓋WebMvcAutoConfiguration的配置
- 繼承DelegatingWebMvcConfiguration:會覆蓋WebMvcAutoConfiguration的配置
-
推薦使用模式
- 非必要,最好避免WebMvcConfigurer,WebMvcConfigurationSupport在一個項目中同時使用;
- 出於安全性攔截配置,建議項目採用WebMvcConfigurer接口的方式做全局配置;
- 日期,時間等建議使用LocalDate,替換歷史的Date數據類型;