最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

在SpringBoot接口中正確地序列化時(shí)間字段的方法

 更新時(shí)間:2024年11月22日 08:43:10   作者:編碼的熊十二  
文章主要介紹在 Spring Boot 接口中正確序列化時(shí)間字段的方法,包括 Java 中Date和LocalDateTime類型的區(qū)別,JSON 序列化和請求參數(shù)中時(shí)間字段的處理,如時(shí)間字符串的格式配置、時(shí)間戳的使用及相關(guān)配置,還提到了在 Swagger UI 中的類型設(shè)置,需要的朋友可以參考下

在 Java 項(xiàng)目中處理時(shí)間序列化從來就不是容易的事。一方面要面臨多變的時(shí)間格式,年月日時(shí)分秒,毫秒,納秒,周,還有討厭的時(shí)區(qū),稍不注意就可能收獲一堆異常,另一方面,Java 又提供了 DateLocalDateTime 兩個(gè)版本的時(shí)間類型,二者分別對應(yīng)著不同的序列化配置,光是弄清楚這些配置,就是一件麻煩事。但是在大多數(shù)時(shí)候,我們又不得不和這一堆配置打交道。

作為開始,讓我們來了解一下可用的配置和相應(yīng)的效果。

時(shí)間字符串配置

準(zhǔn)備一個(gè)簡單接口來展示效果。

@Slf4j
@RestController
@RequestMapping("/boom")
public class BoomController {

    @Operation(summary = "boom")
    @GetMapping
    public BoomData getBoomData() {
        return new BoomData(Clock.systemDefaultZone());
    }

    @Operation(summary = "boom")
    @PostMapping
    public BoomData postBoomData(@RequestBody BoomData boomData) {
        log.info("boomData: {}", boomData);
        return boomData;
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class BoomData {

    private Date date;
    private LocalDateTime localDateTime;
    private LocalDate localDate;
    private LocalTime localTime;

    public BoomData(Clock clock) {
        this.date = new Date(clock.millis());
        this.localDateTime = LocalDateTime.now(clock);
        this.localDate = LocalDate.now(clock);
        this.localTime = LocalTime.now(clock);
    }
}

上面涉及兩種時(shí)間類型:

  • Date 代表老版本日期類型,類似的還有 Calendar,陪著 Java 度過了漫長歲月,使用面極廣。但相對而言,設(shè)計(jì)不太跟得上時(shí)代了,比如值可變導(dǎo)致線程不安全,月份從 0 開始有點(diǎn)不正常。
  • LocalDateTime 代表 java.time 包的新版時(shí)間類型,JDK 8 中引入。新的時(shí)間類型解決老版本類型的設(shè)計(jì)缺陷,同時(shí)增加了豐富的 API 來提高易用性。

兩種類型在記錄的信息方面有一點(diǎn)區(qū)別:

  • Date 的時(shí)間精度為毫秒,內(nèi)部實(shí)際是一個(gè) long 類型時(shí)間戳。此外還記錄了時(shí)區(qū)信息,簡單記錄為 Date = timestamp + timezone。如果沒有提供時(shí)區(qū),默認(rèn)使用系統(tǒng)時(shí)區(qū)。

  • LocalDateTime 時(shí)間精度為納秒,內(nèi)部用 7 個(gè)整數(shù)來記錄時(shí)間:

    • int year
    • short month
    • short day
    • byte hour
    • byte minute
    • byte second
    • int nano

    可以簡單記錄為 LocalDateTime = year + month + day + hour + minute + second + nano。(實(shí)際上應(yīng)該是 LocalDateTime = LocalDate + LocalTime,LocalDate = year + month + day,LocalTime = hour + minute + second + nano。)

    LocalDateTime 沒有時(shí)區(qū)信息,這也是類名中 Local 的含義,代表使用本地時(shí)區(qū)。如果需要時(shí)區(qū)信息,可以用 ZonedDateTime 類型,ZonedDateTime = LocalDateTime + tz。

了解了兩個(gè)版本時(shí)間類型的區(qū)別,再看它們的序列化差異。

JSON 序列化

調(diào)用 GET 接口,得到默認(rèn)的序列化結(jié)果。

{
  "date": "2024-10-10T21:07:08.781+08:00",
  "localDateTime": "2024-10-10T21:07:08.781283",
  "localDate": "2024-10-10",
  "localTime": "21:07:08.781263"
}

默認(rèn)配置下,時(shí)間字段被序列化為時(shí)間字符串,但格式不盡相同。Spring Boot 使用 Jackson 進(jìn)行 JSON 序列化,對不同的時(shí)間類型有不同的格式化規(guī)則:

  • Date 默認(rèn)按照 ISO 標(biāo)準(zhǔn)格式化
  • LocalDateTime 也按照 ISO 標(biāo)準(zhǔn)處理,精確到微秒,少了時(shí)區(qū)
  • LocalDateLocalTime 與 LocalDateTime 處理方式相似

所謂 ISO 標(biāo)準(zhǔn),指的是 ISO 8601 標(biāo)準(zhǔn),一種專門處理日期時(shí)間格式的國際標(biāo)準(zhǔn)。將時(shí)間日期組合按 yyyy-MM-dd'T'HH:mm:ss.SSSXXX 格式處理,比如 2024-10-10T21:07:08.781+08:00,其中字母 T 為日期和時(shí)間分隔符,日期表示為年-月-日,時(shí)間表示為時(shí):分:秒.毫秒。格式中的 XXX 指的是時(shí)區(qū)信息,對于東 8 區(qū),表示為 +08:00。

默認(rèn)情況下,調(diào)用 POST 接口,也需要保證 body 中的 JSON 串按照 ISO 8601 的格式處理時(shí)間字段,才能正常反序列化,否則 Spring Boot 會(huì)拋出異常。當(dāng)然,時(shí)間格式的要求也沒那么嚴(yán)格,可以省略時(shí)區(qū)、微秒、毫秒、秒,都能正常反序列化,但 T 不能省略,年月日時(shí)分不能省略。

在接口調(diào)用兩端統(tǒng)一標(biāo)準(zhǔn)時(shí),ISO 8601 表現(xiàn)不壞,但是,碰到國內(nèi)互聯(lián)網(wǎng)偏愛的 yyyy-MM-dd HH:mm:ss 格式,就會(huì)收獲一個(gè) HttpMessageNotReadableException,JVM 會(huì)提示你 JSON parse error: Cannot deserialize value of type XXX ...。

如果想要加入 yyyy-MM-dd HH:mm:ss 大家庭,最簡單的辦法是使用 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")。@JsonFormat 注解用于指定時(shí)間類型的序列格式,對 Date 類型和 LocalDateTime 類型都有效。

public class BoomData {

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date date;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime localDateTime;
    @JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDate localDate;
    @JsonFormat(pattern = "HH:mm:ss")
    private LocalTime localTime;

}

此時(shí)能 GET 到滿足格式的時(shí)間字符串

{
  "date": "2024-11-20 15:15:57",
  "localDateTime": "2024-11-20 23:15:57",
  "localDate": "2024-11-20",
  "localTime": "23:15:57"
}

POST 請求也正常處理。

看樣子 @JsonFormat 效果不壞。問題是,稍微有點(diǎn)繁瑣,每個(gè)時(shí)間字段都要配置一遍。幸運(yùn)的是,spring boot 支持全局設(shè)置 Jackson 時(shí)間序列化格式:

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss # 全局時(shí)間格式
    time-zone: GMT+8 # 指定默認(rèn)時(shí)區(qū),除非時(shí)間字段已指定時(shí)區(qū),否則 JSON 序列化時(shí)都會(huì)使用此時(shí)區(qū)

更加幸運(yùn)的是,@JsonFormat 優(yōu)先級(jí)比全局配置更高,讓我們可以實(shí)現(xiàn)某些要求特殊格式的需求。

似乎只要組合 spring.jackson.date-format 和 @JsonFormat,我們就可以無所不能了。沒有人比我更懂時(shí)間序列化!

可惜的是,spring.jackson.date-format 不支持新版時(shí)間類型。是的,在 2024 年,距離 java.time 包發(fā)布已經(jīng)十年了,Spring 的序列化配置仍然不支持 LocalDateTime 類型。如果你要序列化 LocalDateTime 類型,最簡單的辦法就是使用 @JsonFormat。因?yàn)?@JsonFormat 是 Jackson 提供的注解。Spring 對此毫無作為。

發(fā)完牢騷,考慮如何全局配置 LocalDateTime 的格式化規(guī)則。方案有很多種,最簡單的就是明確地告訴 Jackson,LocalDateTime 等類型按照某某格式序列化和反序列化。

// 大概是 JacksonConfig 之類的類
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {

    return builder -> {

        // formatter
        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        DateTimeFormatter dateTimeFormatter =  DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");

        // deserializers
        builder.deserializers(new LocalDateDeserializer(dateFormatter));
        builder.deserializers(new LocalDateTimeDeserializer(dateTimeFormatter));
        builder.deserializers(new LocalTimeDeserializer(timeFormatter));

        // serializers
        builder.serializers(new LocalDateSerializer(dateFormatter));
        builder.serializers(new LocalDateTimeSerializer(dateTimeFormatter));
        builder.serializers(new LocalTimeSerializer(timeFormatter));
    };
}

上述代碼為三種類型構(gòu)建了不同的 DateTimeFormatter(java.time 包提供的格式化工具,線程安全),然后為每種類型綁定序列化器(Serializer)和反序列化器(Deserializer)。

現(xiàn)在 Local 系統(tǒng)的日期類型就跟 Date 表現(xiàn)一致了。

總結(jié)一下,在 JSON 序列化時(shí):

  • 如果使用了 Date 類型,可以用 spring.jackson.date-format 和 @JsonFormat 的組合來適應(yīng)不同格式化要求
  • 如果使用了 LocalDateTime 等類型,需要配置 Jackson,綁定序列化器和反序列化器,再結(jié)合 @JsonFormat 方能從心所欲

但此時(shí)還沒結(jié)束,也并非結(jié)束的開始,只是開始的結(jié)束~

請求參數(shù)

除了 JSON 序列化,還有一種場景,也會(huì)涉及時(shí)間序列化。那就是請求參數(shù)中的時(shí)間字段,最常見的就是 Controller 方法中沒有用 @RequestBody 標(biāo)記的對象參數(shù),比如 GET 請求,比如表單提交(application/x-www-form-urlencoded)的 POST 請求。

為了便于展示,在 BoomController 中添加一個(gè)新的接口方法。

@GetMapping("query")
public BoomData queryBoomData(BoomData boomData) {
    log.info("boomData: {}", boomData);
    return boomData;
}

一個(gè)比較常用的 Query 接口的寫法。試著調(diào)用一下。

GET http://localhost:8080/boom/query?localDateTime=2024-10-10T21:07:08.781283&date=2024-10-10T21:07:08.781+08:00

報(bào)錯(cuò),field 'date': rejected value [2024-10-10T21:07:08.781+08:00]。

再試試

GET http://localhost:8080/boom/query?localDateTime=2024-10-10T21:07:08.781283&date=2024-10-10 21:07:08

還是報(bào)錯(cuò),field 'date': rejected value [2024-10-10 21:07:08]。

什么格式才能不報(bào)錯(cuò)?

GET http://localhost:8080/boom/query?localDateTime=2024-10-10T21:07:08.781283&date=10/10/2024 21:07:08

沒錯(cuò),要用 dd/MM/yyyy 的格式。因?yàn)檎埱髤?shù)不歸 JSON 序列化管,而是由 Spring MVC 處理。Spring MVC 默認(rèn)的 Date 類型格式就是 dd/MM/yyyy。

要修改也簡單,@DateTimeFormat,Spring 提供,專門處理時(shí)間參數(shù)格式化。

@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date date;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime localDateTime;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate localDate;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalTime localTime;

現(xiàn)在,正常處理請求 http://localhost:8080/boom/query?localDateTime=2024-10-10 21:07:08&date=2024-10-10 21:07:08&localDate=2024-10-10&localTime=11:09:15。

又到了尋找全局配置的時(shí)間了。

spring:
  mvc:
    format:
      date: yyyy-MM-dd HH:mm:ss # 對 Date 和 LocalDate 類型有效,LocalDate 會(huì)忽略時(shí)間部分
      time: HH:mm:ss # 對 LocalTime 和 OffsetTime 有效
      date-time: yyyy-MM-dd HH:mm:ss # LocalDateTime, OffsetDateTime, and ZonedDateTime

按需選擇即可。

總結(jié)一下,對于 GET 請求參數(shù)中的時(shí)間字段,和表單提交 POST 請求中的時(shí)間字段,可以通過 spring.mvc.format.date/time/date-time 來配置全局格式。

  • 請求中只使用了 Date 類型,只需要配置 spring.mvc.format.date
  • 如果使用了 java.time 包中的類型,需要根據(jù)類型選擇不同配置項(xiàng)

對于不使用全局配置的場景,用 @DateTimeFormat 指定單獨(dú)的時(shí)間格式。

一起來用時(shí)間戳吧

以上是使用時(shí)間字符串傳遞時(shí)間的情況,接下來,我們討論一下用時(shí)間戳格式。

先理解一下有關(guān)時(shí)間戳的概念:

  • GMT 時(shí)間,用來表示時(shí)區(qū),比如 GMT+8,就是指東 8 區(qū)的時(shí)間。單獨(dú)的 GMT 也可以看作 GMT+0,即 0 時(shí)區(qū)的時(shí)間,這個(gè)時(shí)區(qū)位于英國格林威治

  • UTC 時(shí)間,與 GMT 是相同的概念,也用來表示時(shí)區(qū),只不過 UTC 更精確一些。同樣,UTC+8 可以表示東 8 區(qū),單獨(dú) UTC 表示 0 時(shí)區(qū)

  • Unix 紀(jì) 元(Unix Epoch),一個(gè)特定的時(shí)間點(diǎn),1970 年 1 月 1 日 00:00:00 UTC(+0),也就是 0 時(shí)區(qū)中 1970 年元旦。這個(gè)時(shí)間點(diǎn)常用于計(jì)算機(jī)系統(tǒng)的時(shí)間起點(diǎn),如同坐標(biāo)軸上的 0。

指導(dǎo)了上述 3 個(gè)概念,時(shí)間戳的含義就容易解釋了,從 Unix 紀(jì) 元開始經(jīng)過的毫秒數(shù)(或秒數(shù),計(jì)算機(jī)常用毫秒)。把時(shí)間想象為一條長長的坐標(biāo)軸,0 的位置是 Unix 紀(jì) 元,在那之后,真實(shí)世界的每一毫秒,都對應(yīng)時(shí)間軸上的一個(gè)點(diǎn)。

時(shí)間戳用整數(shù)表示,一個(gè)長整數(shù),具備時(shí)間字符串一樣的功能。因此,也可以用時(shí)間戳來傳遞時(shí)間信息。

如果我信誓旦旦地宣稱時(shí)間戳優(yōu)于時(shí)間字符串,肯定是十分主觀的判斷,但在接口中使用時(shí)間戳確實(shí)有一些亮晶晶的優(yōu)點(diǎn)。

  • 時(shí)區(qū)無關(guān)性,時(shí)間戳的值固定為 UTC+0 時(shí)區(qū),無論位于哪個(gè)時(shí)區(qū),同一時(shí)刻,同一時(shí)間戳。這樣一來,就可以僅展示時(shí)考慮時(shí)區(qū),其他時(shí)候都不需要考慮時(shí)區(qū)
  • 體積小,一個(gè) long 值足矣,比時(shí)間字符串更簡短
  • 兼容性好,不必考慮復(fù)雜的格式化規(guī)則

一些不可忽視的缺點(diǎn):

  • 可讀性差,時(shí)間戳沒有時(shí)間字符串直觀,需要一些輔助轉(zhuǎn)換工具,比如瀏覽器控制臺(tái)
  • 秒級(jí)時(shí)間戳和毫秒時(shí)間戳可能混淆,使用前要約定好

用 long 型時(shí)間戳也不需要考慮序列化問題,大多數(shù)平臺(tái)都可以妥善處理 long 類型的序列化。但有些時(shí)候,在代碼中用 Date 和 LocalDateTime 等明確的類型還是比 long 更方便。所以可能有這么一個(gè)需求:在代碼中使用時(shí)間類型,在序列化時(shí)使用時(shí)間戳。也就是在 DTO 類中用 Date,在 JSON 字符串中用 long。

和使用時(shí)間字符串類型,這個(gè)需求也分為兩種情況:

  • JSON 序列化轉(zhuǎn)換
  • 請求參數(shù)轉(zhuǎn)換

二者要分開處理。

JSON 序列化中的時(shí)間戳

Spring 提供了一個(gè)配置項(xiàng),控制 Jackson 在序列化時(shí)將時(shí)間類型處理為時(shí)間戳。

spring.jackson.serialization.write-dates-as-timestamps=true

此時(shí),GET 請求中的 date 就會(huì)變成了 "date": 1728572627475,POST 時(shí)也能正確地識(shí)別時(shí)間戳。

但是,只有 Date 才有這種優(yōu)渥的待遇,java.time 包的類型仍然面臨自己動(dòng)手豐衣足食的窘境。

開啟 write-dates-as-timestamps 后,LocalDateTime 等類型會(huì)被序列化為整形數(shù)組(回憶一下 LocalDateTime 的簡單公式)。

{
  "date": 1728572627475,
  "localDateTime": [
    2024,
    10,
    10,
    23,
    3,
    47,
    475519000
  ],
  "localDate": [
    2024,
    10,
    10
  ],
  "localTime": [
    23,
    3,
    47,
    475564000
  ]
}

也不能說有問題,畢竟 LocalDateTime 精確到納秒,直接轉(zhuǎn)換為毫秒時(shí)間戳,會(huì)丟失精度??傊?,要實(shí)現(xiàn)和諧轉(zhuǎn)換,需要設(shè)置 Jackson。

// 仍然是 JacksonConfig 之類的什么地方
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
    return builder -> {
        // deserializers
        builder.deserializers(new LocalDateDeserializer());
        builder.deserializers(new LocalDateTimeDeserializer());

        // serializers
        builder.serializers(new LocalDateSerializer());
        builder.serializers(new LocalDateTimeSerializer());
    };
}

public static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {

    /**
     * 如果沒有重寫 handledType() 方法,會(huì)報(bào)錯(cuò)
     * @return LocalDateTime.class
     */
    @Override
    public Class<LocalDateTime> handledType() {
        return LocalDateTime.class;
    }

    @Override
    public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers)
            throws IOException {
        if (value != null) {
            gen.writeNumber(value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
        }
    }
}

public static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {

    @Override
    public Class<?> handledType() {
        return LocalDateTime.class;
    }

    @Override
    public LocalDateTime deserialize(JsonParser parser, DeserializationContext deserializationContext) throws IOException {
        long timestamp = parser.getValueAsLong();
        return Instant.ofEpochMilli(timestamp).atZone(ZoneId.systemDefault()).toLocalDateTime();
    }
}

public static class LocalDateSerializer extends JsonSerializer<LocalDate> {

    @Override
    public Class<LocalDate> handledType() {
        return LocalDate.class;
    }

    @Override
    public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializers)
            throws IOException {
        if (value != null) {
            gen.writeNumber(value.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli());
        }
    }
}

public static class LocalDateDeserializer extends JsonDeserializer<LocalDate> {

    @Override
    public Class<?> handledType() {
        return LocalDate.class;
    }

    @Override
    public LocalDate deserialize(JsonParser parser, DeserializationContext deserializationContext) throws IOException {
        long timestamp = parser.getValueAsLong();
        return Instant.ofEpochMilli(timestamp).atZone(ZoneId.systemDefault()).toLocalDate();
    }
}

這里我們進(jìn)行了一些生硬的強(qiáng)制措施,定義了一系列 Deserializer 和 Serializer,實(shí)現(xiàn)了 LocalDateTime 和 long 之間的序列化規(guī)則。

沒有處理 LocalTime,因?yàn)閱为?dú)的時(shí)間轉(zhuǎn)換為時(shí)間戳不那么契合,時(shí)間戳有明確地年月日,這部分對于 LocalTime 顯得多余,而且時(shí)間通常與時(shí)區(qū)有關(guān),處理時(shí)要更謹(jǐn)慎一些。可以根據(jù)需求選擇,如果明確需要使用時(shí)間戳來表示 LocalTime,可以采用類似的方法,注冊 Deserializer 和 Serializer。

以上是在 JSON 序列化時(shí)將 Date、LocalDateTime 轉(zhuǎn)化為時(shí)間戳需要的配置:

  • 如果只使用 Date,使用 Spring 提供的配置項(xiàng) spring.jackson.serialization.write-dates-as-timestamps=true 即可
  • 如果使用了 LocalDateTime,需要進(jìn)行額外的配置,明確地指定 Jackson 將 LocalDateTime 轉(zhuǎn)換為時(shí)間戳

請求參數(shù)中的時(shí)間戳

在請求參數(shù)中使用時(shí)間戳復(fù)雜一些,因?yàn)椴幌駮r(shí)間字符串一樣有現(xiàn)成的配置,需要手動(dòng)實(shí)現(xiàn)轉(zhuǎn)換規(guī)則。

可以利用 Converter 接口來解決這個(gè)問題。

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new LongStringToDateConverter());
        registry.addConverter(new LongStringToLocalDateTimeConverter());
        registry.addConverter(new LongStringToLocalDateConverter());
        // registry.addConverter(new LongStringToLocalTimeConverter()); // 按需
    }

    private static class LongStringToDateConverter implements Converter<String, Date> {
        @Override
        public Date convert(String source) {
            try {
                long timestamp = Long.parseLong(source);
                return new Date(timestamp);
            } catch (NumberFormatException e) {
                return null;
            }
        }
    }

    private static class LongStringToLocalDateTimeConverter implements Converter<String, LocalDateTime> {

        @Override
        public LocalDateTime convert(String source) {
            try {
                long timestamp = Long.parseLong(source);
                return Instant.ofEpochMilli(timestamp).atZone(ZoneId.systemDefault()).toLocalDateTime();
            } catch (NumberFormatException e) {
                return null;
            }
        }
    }

    private static class LongStringToLocalDateConverter implements Converter<String, LocalDate> {

        @Override
        public LocalDate convert(String source) {
            try {
                long timestamp = Long.parseLong(source);
                return Instant.ofEpochMilli(timestamp).atZone(ZoneId.systemDefault()).toLocalDate();
            } catch (NumberFormatException e) {
                return null;
            }
        }
    }

    private static class LongStringToLocalTimeConverter implements Converter<String, LocalTime> {

        @Override
        public LocalTime convert(String source) {
            try {
                long timestamp = Long.parseLong(source);
                return Instant.ofEpochMilli(timestamp).atZone(ZoneId.systemDefault()).toLocalTime();
            } catch (NumberFormatException e) {
                return null;
            }
        }
    }

}

注意 Source 類型為 String 而不是 Long,因?yàn)?Spring MVC 會(huì)將所有的接口請求參數(shù)類型統(tǒng)一視為 String,然后調(diào)用 Converter 轉(zhuǎn)換為其他類型。有許多內(nèi)置的 Converter,比如轉(zhuǎn)換為 long 類型時(shí),就使用了內(nèi)置的 StringToNumber 轉(zhuǎn)換類。我們定義的 LongStringToDateConverter 與 StringToNumber 是平級(jí)的關(guān)系。

以上是在接口參數(shù)中將 Date、LocalDateTime 轉(zhuǎn)化為時(shí)間戳需要的處理:很簡單,注冊 Converter 即可。

Swagger UI 中的類型

使用 SwaggerUI 時(shí),默認(rèn)會(huì)使用 DTO 字段類型作為請求參數(shù)類型,也就是接收時(shí)間字符串。序列化時(shí)改為時(shí)間戳后,還需要在 Swagger UI 中統(tǒng)一。

Java 項(xiàng)目有兩種集成 Swagger 的方式,Springdoc 和 Spring Fox。Springdoc 更新,對應(yīng)的配置如下:

@Bean
public OpenAPI customOpenAPI() {
    // 關(guān)鍵是要調(diào)用這個(gè)靜態(tài)方法進(jìn)行 replace
    SpringDocUtils.getConfig()
            .replaceWithClass(Date.class, Long.class)
            .replaceWithClass(LocalDateTime.class, Long.class)
            .replaceWithClass(LocalDate.class, Long.class);
    return new OpenAPI();
}

如果使用 Spring Fox,則需要使用另一種配置:

@Bean
public Docket createRestApi() {
    return new Docket(DocumentationType.OAS_30)
            ...
            .build()
            // 重點(diǎn)是這句
            .directModelSubstitute(LocalDateTime.class, Long.class);
}

此時(shí),在 Swagger UI 頁面調(diào)試接口時(shí),時(shí)間類型的參數(shù)就顯示為整數(shù)了。

The Only Neat Thing to Do

回顧一下,在 Spring Boot 接口中處理時(shí)間字段序列化,涉及兩個(gè)場景:

  • JSON 序列化
  • GET 請求和表單提交請求中的參數(shù)

兩種情況要分開設(shè)置。

在 Java 類型選擇方面,Spring 對 Date 類型的支持比 LocalDateTime 好,有很多內(nèi)置的配置,能省去很多麻煩。

如果要使用 LocalDateTime 等類型,在 JSON 序列化時(shí)要指定時(shí)間格式,在請求參數(shù)中也要指定時(shí)間格式。前者需要手動(dòng)配置,后者可以使用 Spring 提供的配置項(xiàng)。

如果想要用時(shí)間戳傳遞數(shù)據(jù),也需要分別設(shè)置,在 JSON 序列化時(shí)指定序列化器和反序列化器,在請求參數(shù)中綁定對應(yīng)的 Converter 實(shí)現(xiàn)類。此外,統(tǒng)一 Swagger UI 的類型體驗(yàn)更佳。

以上就是在Spring Boot接口中正確地序列化時(shí)間字段的方法的詳細(xì)內(nèi)容,更多關(guān)于Spring Boot序列化時(shí)間字段的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • spring security如何擴(kuò)展自定義登錄

    spring security如何擴(kuò)展自定義登錄

    本文詳細(xì)介紹了Spring Security的認(rèn)證原理和具體實(shí)現(xiàn),認(rèn)證原理基于過濾器鏈,通過驗(yàn)證用戶憑證和構(gòu)建認(rèn)證對象來保護(hù)應(yīng)用程序資源,實(shí)現(xiàn)自定義認(rèn)證功能的步驟包括創(chuàng)建自定義認(rèn)證提供程序、實(shí)現(xiàn)UserDetailsService接口以及在配置類中進(jìn)行相應(yīng)的配置
    2024-11-11
  • J2EE Servlet基礎(chǔ)在瀏覽器上運(yùn)行HelloServlet的方法

    J2EE Servlet基礎(chǔ)在瀏覽器上運(yùn)行HelloServlet的方法

    這篇文章主要介紹了J2EE Servlet基礎(chǔ)在瀏覽器上運(yùn)行HelloServlet的方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-10-10
  • eclipse java工程改造為java web工程詳解

    eclipse java工程改造為java web工程詳解

    這篇文章主要介紹了eclipse java工程改造為java web工程詳解的相關(guān)資料,需要的朋友可以參考下
    2017-05-05
  • SpringBoot?支付渠道實(shí)現(xiàn)統(tǒng)一的支付服務(wù)示例

    SpringBoot?支付渠道實(shí)現(xiàn)統(tǒng)一的支付服務(wù)示例

    本文提出了一種基于工廠模式和策略模式的多渠道支付封裝方案,該設(shè)計(jì)采用策略模式實(shí)現(xiàn)各支付渠道(微信、支付寶、銀聯(lián))的統(tǒng)一接口,通過工廠模式動(dòng)態(tài)創(chuàng)建支付策略,感興趣的可以了解一下
    2026-03-03
  • SpringBoot監(jiān)聽Redis key失效事件的實(shí)現(xiàn)代碼

    SpringBoot監(jiān)聽Redis key失效事件的實(shí)現(xiàn)代碼

    這篇文章給大家介紹了SpringBoot實(shí)現(xiàn)監(jiān)聽Redis key失效事件的方法,文中通過代碼示例給大家講解的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下
    2024-02-02
  • Spring boot集中異常處理方法實(shí)例

    Spring boot集中異常處理方法實(shí)例

    這篇文章主要介紹了Spring boot集中異常處理方法實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04
  • SpringBoot+Redis+Lua實(shí)現(xiàn)接口限流的示例代碼

    SpringBoot+Redis+Lua實(shí)現(xiàn)接口限流的示例代碼

    本文主要介紹了SpringBoot+Redis+Lua實(shí)現(xiàn)接口限流的示例代碼,利用AOP攔截請求,通過Redis記錄訪問次數(shù),達(dá)到限流效果,具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-09-09
  • Maven項(xiàng)目部署到服務(wù)器設(shè)置訪問路徑以及配置虛擬目錄的方法

    Maven項(xiàng)目部署到服務(wù)器設(shè)置訪問路徑以及配置虛擬目錄的方法

    今天小編就為大家分享一篇關(guān)于Maven項(xiàng)目部署到服務(wù)器設(shè)置訪問路徑以及配置虛擬目錄的方法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-02-02
  • Java String字符串的常用使用方法

    Java String字符串的常用使用方法

    String是JDK提供的一個(gè)類,是引用類型,并不是基本的數(shù)據(jù)類型,String用于字符串操作,在之前學(xué)習(xí)c語言的時(shí)候,對于一些字符串,會(huì)初始化字符數(shù)組表示,本文給大家介紹Java String字符串的常用使用方法,感興趣的朋友一起看看吧
    2025-04-04
  • java基于移位操作實(shí)現(xiàn)二進(jìn)制處理的方法示例

    java基于移位操作實(shí)現(xiàn)二進(jìn)制處理的方法示例

    這篇文章主要介紹了java基于移位操作實(shí)現(xiàn)二進(jìn)制處理的方法,結(jié)合實(shí)例形式分析了java針對二進(jìn)制的移位操作處理技巧,需要的朋友可以參考下
    2017-02-02

最新評論

沂南县| 长沙县| 泰州市| 略阳县| 思茅市| 化德县| 青州市| 永登县| 长治县| 呼玛县| 古浪县| 修水县| 泰宁县| 宁南县| 西昌市| 嘉禾县| 称多县| 襄汾县| 读书| 漠河县| 鹤壁市| 舞钢市| 平阴县| 民勤县| 河北区| 巫溪县| 金门县| 维西| 贵定县| 嘉荫县| 开江县| 双鸭山市| 遂溪县| 邵东县| 灵武市| 莲花县| 南和县| 保康县| 南涧| 宁安市| 康定县|