1. 程式人生 > 其它 >日期格式化時註解@DateTimeFormat無效的問題分析

日期格式化時註解@DateTimeFormat無效的問題分析

日期格式化時註解@DateTimeFormat無效的問題分析

背景

有時候我們在寫介面時,需要把前臺傳來的日期String型別轉為Date型別

這時我們可能會用到@DateTimeFormat註解

在請求資料為非JSON格式時,這個註解是沒有問題的,可用的;

但是當請求資料為JSON格式時,問題就出現了

此時如果請求引數沒有加@RequestBody註解,那麼請求引數不會執行型別轉換操作,資料都是預設為空(基本型別比如int = 0, 物件引用比如Date date= null)
此時如果請求引數有加@RequestBody註解,那麼請求引數會執行JSON型別轉換操作,但是轉換會提示異常
所以文章題目中所說的有時無效,指的就是上面這兩種情況

目錄

本文分三步走,如下所示,其中會穿插著介紹@DateTimeFormat、@RequestBody、@JsonFormat註解

分析

1. 基礎程式碼:

AnnationApplication.java:主程式兼控制器

package com.jalon.annation;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController public class AnnationApplication { public static void main(String[] args) { SpringApplication.run(AnnationApplication.class, args); } @PostMapping(
"/personPost") public Person personPost(Person person){ System.out.println(person); return person; } }

Person.java 實體類

package com.jalon.annation;

import com.fasterxml.jackson.annotation.JacksonAnnotation;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Component;

import java.util.Date;

public class Person {

    private int age;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date birth;

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", birth=" + birth +
                '}';
    }
 // 省略getter/setter
}

2. 案例分析:

這裡我們用的是PostMan進行測試,請求示例如下

所有示例全程都有@DateTimeFormat註解

示例1:

  • 請求方式:Post請求

  • 資料格式:非JSON格式,比如form-data

  • 請求資源:personPost(Person person),無@RequestBody註解

    具體請求內容和返回結果如下所示

@ResponseBody對應於@RequestBody;

  • 前者負責將Java物件序列號成JSON資料進行返回
  • 後者負責解析請求過來的JSON資料,解析成對應的Java物件

我們再來看下後臺,列印如下:

Person{age=1, birth=Wed Jan 01 00:00:00 CST 2020}

可以看到,後臺列印正常(資料無誤,日期格式忽略,因為這裡的date.toString用的Date的預設方法)

從上面的結果我們可以看到,@DateTimeFormat只是負責解析傳來的日期字串,轉為對應的日期物件;

但是並不會修改原有的日期物件的格式(從前臺返回和後臺輸出可以看到,日期格式不受@DateTimeFormat的影響)

示例2:

  • 請求方式:Post請求

  • 資料格式:JSON格式,比如application/json

  • 請求資源:personPost(Person person),無@RequestBody註解

    具體請求內容和返回結果如下所示

可以看到,返回資料都為空(預設的初始值),說明資料都沒有傳過去,不止是date,連基本型別int都沒過去

我們再來看下後臺,列印如下

Person{age=0, birth=null} // 跟前臺返回的資料一致

可以看到,後臺解析到的資料也是空的,所以上面返回的當然是空的

原因就是預設的型別轉換器是沒有轉化成JSON格式的對應轉換類的,部分轉換器如下所示,(core.convert.support包)

解決:所以這裡對應的解決辦法就是,自己建立一個JSON轉換器

但是實際上這個已經有實現了,只是沒有觸發,如下所示的構建工具(http.converter.json包),就是用來配置相關的json序列化和反序列化的

現在我們可以通過@RequestBody註解來觸發,它在接收到JSON格式的資料時,會自動呼叫對應的JSON轉換器

下面的示例3就是這個例子

加了@RequestBody後,預設只接受application/json格式的資料,如果傳入其他格式,會報415不支援的型別

示例3:

  • 請求方式:Post請求

  • 資料格式:JSON格式,比如application/json

  • 請求資源:personPost(@RequestBody Person person),有@RequestBody註解

    具體請求內容和返回結果如下所示

可以看到,報錯了,提示400,這種一般屬於客戶端錯誤(比如資料格式不正確,資料過大等)

我們再來看下後臺,列印如下

2021-05-15 13:48:41.578  WARN 38426 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved 
[org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot 
deserialize value of type `java.util.Date` from String "2020-01-01 00:00:00": not a valid 
representation (error: Failed to parse Date value '2020-01-01 00:00:00': Cannot parse date "2020-
01-01 00:00:00": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSX', parsing fails 
(leniency? null)); nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException:
 Cannot deserialize value of type `java.util.Date` from String "2020-01-01 00:00:00": not a valid 
 representation (error: Failed to parse Date value '2020-01-01 00:00:00': Cannot parse date 
 "2020-01-01 00:00:00": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSX', parsing fails 
 (leniency? null))
 at [Source: (PushbackInputStream); line: 3, column: 14] (through reference chain: 
 com.jalon.annation.Person["birth"])]

這裡我們提取關鍵的部分來看:

1. nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot 
deserialize value of type `java.util.Date` from String "2020-01-01 00:00:00"
 
2. Cannot parse date "2020-01-01 00:00:00": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSX'

首先這裡跟示例2不同,這裡起碼做了嘗試轉換,只是沒有找到對應的格式,所以轉換失敗了

可以看到,它並沒有按照上面我們的@DateTimeFormat註解去解析,而是按照’'yyyy-MM-dd’T’HH:mm:ss.SSSX"這個格式去解析

這裡如果想投機的話,可以在前臺直接傳入’'yyyy-MM-dd’T’HH:mm:ss.SSSX’格式的資料,如下:

但是這種辦法對於前端很不友好(極其不好)

所以下面還是給出正常的解決辦法

解決:所以這裡的解決辦法就是自己定義日期格式

  • 方案一:區域性註解來解決,比如在date欄位新增@JsonFormat()註解
// 這個註解用來解析JSON資料中的日期字串,會序列化返回資料
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date birth;
區域性的特點:靈活,但是配置繁瑣,不統一(每個欄位都要加)
  • 方案二:全域性配置來解決,比如配置一個Jackson2ObjectMapperBuilderCustomizer,然後自定義日期反序列化格式
package com.jalon.annation;

import com.fasterxml.jackson.databind.deser.std.DateDeserializers;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

import java.text.SimpleDateFormat;
import java.util.Date;

@Configuration
public class MyDateConvertCustoms implements Jackson2ObjectMapperBuilderCustomizer {
    @Override
    public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
            // 覆蓋預設的Date反序列化,第一個引數為需要反序列化的類,第二個為具體的序列化格式
      jacksonObjectMapperBuilder.deserializerByType(
                Date.class
                ,new DateDeserializers.DateDeserializer(
                        DateDeserializers.DateDeserializer.instance
                        , new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                        , null));
    }
}
全域性的特點:不靈活,但是直觀清晰,配置統一

3. 結論分析:

主要根據請求的資料型別來對比

請求非JSON資料,建議用@DateTimeFormat即可(比如get請求,當然get請求也可以請求JSON資料,只是不推薦)
請求JSON資料,建議用@ReqeustBody來轉換資料,然後搭配區域性註解@JsonFormat或者全域性配置來修改預設的日期解析格式(預設"yyyy-MM-dd’T’HH:mm:ss.SSSX")

總結

註解相關:

@DateTimeFormat註解:適用於請求資料為非JSON資料,不會格式化返回資料
@JsonFormat註解:適用於請求資料為JSON資料(尤其有日期資料時),且需在請求方法的引數前加@RequestBody`註解,會格式化返回資料
@RequestBody註解:解析傳來的JSON資料,轉換成對應的Java物件
@ResponseBody註解:轉換Java物件為JSON資料,用來作為返回資料輸出到前端

日期格式化相關:

請求非JSON資料,建議用@DateTimeFormat即可,此時不會格式化返回資料(比如get請求,當然get請求也可以請求JSON資料,只是不推薦)
請求JSON資料,建議用@ReqeustBody來轉換資料,然後搭配區域性註解@JsonFormat(會格式化返回資料)或者全域性配置來修改預設的日期解析格式(預設"yyyy-MM-dd’T’HH:mm:ss.SSSX");全域性配置也可以格式化返回資料,需配置builder.serializerByType
如果日期格式化出錯,先看傳來的資料是否為JSON資料(可以通過consumes來限制),然後再看有沒有對於的註解或日期格式化全域性配置

轉載:https://blog.csdn.net/qq_43842093/article/details/121986444

TRANSLATE with x English
Arabic Hebrew Polish
Bulgarian Hindi Portuguese
Catalan Hmong Daw Romanian
Chinese Simplified Hungarian Russian
Chinese Traditional Indonesian Slovak
Czech Italian Slovenian
Danish Japanese Spanish
Dutch Klingon Swedish
English Korean Thai
Estonian Latvian Turkish
Finnish Lithuanian Ukrainian
French Malay Urdu
German Maltese Vietnamese
Greek Norwegian Welsh
Haitian Creole Persian
TRANSLATE with COPY THE URL BELOW Back EMBED THE SNIPPET BELOW IN YOUR SITE Enable collaborative features and customize widget: Bing Webmaster Portal Back 帶著疑問去思考,然後串聯,進而歸納總結,不斷追問自己,進行自我辯證,像偵查嫌疑案件一樣看待技術問題,漆黑的街道,你我一起尋找線索,你就是技術界大偵探福爾摩斯