痛點
在使用Spring mvc 進行開發時我們經常遇到前端傳來的某種格式的時間字串無法用java8的新特性java.time包下的具體型別引數來直接接收, 我們使用含有java.time封裝型別的引數接收也會報反序列化問題,在回傳前端帶時間型別的同樣會出現一些格式化的問題,今天我們來徹底解決他們,
建議
其實最科學的建議統一使用時間戳來代表時間,這個是最完美的,避免了前端瀏覽器的兼容性問題,同時也避免了其它一些中間件的序列化/反序列化問題,但是用時間表達可能更清晰語意化,兩種方式各有千秋,如果我們堅持使用java8的時間類別庫也不是沒有辦法,下面我們會以java.time.LocalDateTime為例逐一解決這些問題,
區域注解方式
網上有很多文章說該注解是前端指向后端的,也就是前端向后端傳遞時間引數格式化使用的,這沒有錯!但是有一個小問題,該方式只能適用于不涉及反序列化的情況下,也就是以下場景才適用:
@GetMapping("/local")
public Map<String, String> data(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime localDateTime) {
Map<String, String> map = new HashMap<>(1);
map.put("data", localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
return map;
}
如果你在下面這個場景使用就不行了:
@Data
public 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;
}
原因是Post請求引數在body中,需要反序列化成物件,默認是jackson類別庫來進行反序列化,并不觸發@DateTimeFormat注解機制,
這時我們就需要使用jackson的格式化注解@JsonFormat,我們將物體類UserInfo改造成下面的就可以了:
@Data
public 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;
}
以上兩個注解可以并存,但是一定要清楚各自的使用場景,這里還有一個小細節:格式一定要對應好時間型別,比如yyyy-MM-dd 對應java.time.LocalDate ,如果再個性化一些@JsonFormat 可以被@JsonDeserialize和@JsonSerialize 代替,但是它們的using引數需要你自己實作為你對應的時間型別型別,
如果@JsonFormat、@JsonDeserialize和@JsonSerialize同時存在@JsonFormat的優先級要更高,
區域處理的好處
區域處理的好處在于八個字:百花齊放,百家爭鳴 ,可以保持多樣性、個性化 ,但是區域帶來了一個新的問題 :沒有共同的標準 、不兼容,進而不方便維護,所以有時候基于業務需要我們全域化可以統一管理,下面我們將講解如何進行全域化配置,
全域化化時間格式配置
全域化其實也是基于 @DateTimeFormat 和@JsonFormat 兩種場景來進行配置,對于@DateTimeFormat的場景我們通過實作Spring提供的介面:
DateTimeFormatter :
// 時間格式化
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.CHINA);
型別轉換介面:
org.springframework.core.convert.converter.Converter<S,T>
實作:
@Bean
public Converter<String, LocalDateTime> localDateConverter() {
return new Converter<String, LocalDateTime>() {
@Override
public LocalDateTime convert(String source) {
return LocalDateTime.parse(source, FORMATTER);
}
};
}
或者格式化介面:
org.springframework.format.Formatter<T>
實作 :
@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);
}
};
}
以上兩個介面的實作都要注冊為Spring Bean,配置的時候二者選其一即可,其中S即Source也就是來源,其實就是前端的時間字串,T即Target也就是目標,代表你需要轉化或者格式化的時間java型別,
那么對于時間序列化和反序列化我們進行如下配置就行了(基于默認jackson,以LocalDateTime 為例):
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder
// 反序列化
.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(FORMATTER))
// 序列化
.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(FORMATTER));
}
同樣該jsonMapper自定義構建器要注冊成Spring Bean才行,
全域配置要點
全域配置的一些優缺點上面已經闡述了,這里我還是要啰嗦一下要點避免你踩坑,全域配置跟區域配置一樣,同樣要約定pattern,這就要求我們全域保持一致,我們可以實作多個以上的全域配置來對其他諸如LocalDate、OffsetDateTime 的適配,同時如果我們接入了其它一些需要用到序列化/反序列化的中間件,比如redis、rabbitmq,我們也要注意進行適配,
總結
通過以上對時間格式的區域和全域處理方式的介紹,相信困擾你的Spring mvc 時間問題不會再存在了,如果感覺寫對可以請轉發告訴其他同學,點個贊,關注一下,
關注公眾號:碼農小胖哥 獲取更多資訊
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/95211.html
標籤:其他
