在使用Spring mvc 進行開發時咱們常常遇到前端傳來的某種格式的時間字符串沒法用java8的新特性java.time
包下的具體類型參數來直接接收。 咱們使用含有java.time
封裝類型 的參數接收也會報反序列化問題,在返回前端帶時間類型的一樣會出現一些格式化的問題。今天咱們來完全解決他們。前端
其實最科學的建議統一使用時間戳來表明時間。這個是最完美的,避免了前端瀏覽器的兼容性問題,同時也避免了其它一些中間件的序列化/反序列化問題。可是用時間表達可能更清晰語義化。 兩種方式各有千秋,若是咱們堅持使用java8的時間類庫也不是沒有辦法。下面咱們會以java.time.LocalDateTime
逐一解決這些問題。java
網上有不少文章說該註解是前端指向後端的,也就是前端向後端傳遞時間參數格式化使用的,這沒有錯!可是有一個小問題,該方式只能適用於不涉及反序列化的狀況下。也就是如下場景才適用:redis
@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;
}
複製代碼
若是你在下面這個場景使用就不行了:spring
@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
改形成下面的就能夠了:json
@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提供的接口:mvc
DateTimeFormatter :app
// 時間格式化
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.CHINA);
複製代碼
類型轉換接口:ide
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 時間問題不會再存在了。若是感受寫對能夠請轉發告訴其餘同窗,點個贊,關注一下。
關注公衆號:碼農小胖哥,獲取更多資訊