原文地址:https://mp.weixin.qq.com/s/kWaOZag6w8KMzieEFmIgOw
痛點:
在使用Spring mvc 進行開發時我們經常遇到前端傳來的某種格式的時間字符串無法用java8時間包下的具體類型參數來直接接收。同時還有一系列的序列化 、反序列化問題,在返回前端帶時間類型的同樣會出現一些格式化的問題。今天我們來徹底解決他們。
建議:
其實最科學的建議統一使用時間戳來代表時間。這個是最完美的,避免了前端瀏覽器的兼容性問題,同時也避免了其它一些中間件的序列化/反序列化問題。但是用時間表達可能更清晰語義化。兩種方式各有千秋,如果我們堅持使用java8的時間類庫也不是沒有辦法。下面我們會以`java.time.LocalDateTime` 為例逐一解決這些問題。
局部註解:
網上有很多文章說該註解是前端指向後端的,也就是前端向後端傳遞時間參數格式化使用的,這沒有錯!但是有一個小問題,該方式只能適用於不涉及反序列化的情況下。也就是以下場景才適用:
<code>@GetMapping("/local")public Map<string> data(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime localDateTime) { Map<string> map = new HashMap<>(1); map.put("data", localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); return map;}/<string>/<string>/<code>
如果你在下面這個場景使用就不行了:
<code>@Datapublic class UserInfo { @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime birthday; private String name; private Integer age;}@PostMapping("/user")public Object postData(@RequestBody UserInfo userInfo) { System.out.println("userInfo = " + userInfo); return userInfo; }/<code>
原因是Post請求參數在body中,需要反序列化成對象。默認是jackson類庫來進行反序列化,並不觸發`@DateTimeFormat`註解機制。
這時我們就需要使用jackson的格式化註解`@JsonFormat`。我們將實體類`UserInfo`改造成下面的就可以了:
<code>@Datapublic class UserInfo { @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private LocalDateTime birthday; private String name; private Integer age;}/<code>
以上兩個註解可以並存,但是一定要清楚各自的使用場景。這裡還有一個小細節:格式一定要對應好時間類型。比如`yyyy-MM-dd` 對應`java.time.LocalDate` 。如果再個性化一些`@JsonFormat` 可以被`@JsonDeserialize`和`@JsonSerialize` 代替。但是它們的`using`參數需要你自己實現為你對應的時間類型類型。如果`@JsonFormat`、`@JsonDeserialize`和`@JsonSerialize`同時存在`@JsonFormat`的優先級要更高。
局部註解的好處:局部處理的好處在於八個字:百花齊放,百家爭鳴 。可以保持多樣性、個性化 。但是局部帶來了一個新的問題 :沒有共同的標準 、不兼容。進而不方便維護。所以有時候基於業務需要我們全局化可以統一管理。下面我們將講解如何進行全局化配置。
全局配置:
全局化其實也是基於 `@DateTimeFormat` 和`@JsonFormat` 兩種場景來進行配置。對於`@DateTimeFormat`的場景我們通過實現Spring提供的接口:
DateTimeFormatter :
<code>// 時間格式化private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.CHINA);/<code>
類型轉換接口:
<code>org.springframework.core.convert.converter.Converter/<code>
實現:
<code>@Beanpublic Converter<string> localDateConverter() { return new Converter<string>() { @Override public LocalDateTime convert(String source) { return LocalDateTime.parse(source, FORMATTER); } }; }/<string>/<string>/<code>
或者格式化接口:
<code>org.springframework.format.Formatter/<code>
實現 :
<code>@Bean public Formatter<localdatetime> localDateFormatter() { return new Formatter<localdatetime>() { @Override public LocalDateTime parse(String text, Locale locale) throws ParseException { return LocalDateTime.parse(text, FORMATTER); } @Override public String print(LocalDateTime object, Locale locale) { return object.format(FORMATTER); } }; }/<localdatetime>/<localdatetime>/<code>
以上兩個接口的實現都要註冊為Spring Bean,配置的時候二者選其一即可,其中S即Source也就是來源,其實就是前端的時間字符串。T即Target也就是目標,代表你需要轉化或者格式化的時間java類型。那麼對於時間序列化和反序列化我們進行如下配置就行了(基於默認jackson,以LocalDateTime 為例):
<code>@Beanpublic Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder // 反序列化 .deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(FORMATTER)) // 序列化 .serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(FORMATTER));}/<code>
同樣該jsonMapper自定義構建器要註冊成Spring Bean才行。
全局配置的要點:全局配置的一些優缺點上面已經闡述了,這裡我還是要囉嗦一下要點避免你踩坑。全局配置跟局部配置一樣。同樣要約定pattern。這就要求我們全局保持一致。我們可以實現多個以上的全局配置來對其他諸如`LocalDate`、`OffsetDateTime` 的適配。同時如果我們接入了其它一些需要用到序列化/反序列化的中間件,比如redis、rabbitmq,我們也要注意進行適配。
總結:總結通過以上對時間格式的局部和全局處理方式的介紹,相信困擾你的Spring mvc 時間問題不會再存在了。如果感覺寫的可以請轉發告訴其他同學,點個贊,關注一下。
我目前是在職Java開發,如果你現在正在瞭解Java技術,想要學好Java,渴望成為一名Java開發工程師,在入門學習Java的過程當中缺乏基礎的入門視頻教程,你可以關注並私信我:01。我這裡有一套最新的Java基礎JavaSE的精講視頻教程,這套視頻教程是我在年初的時候,根據市場技術棧需求錄製的,非常的系統完整。
閱讀更多 Java架構人生 的文章