在SpringBoot接口中正確地序列化時(shí)間字段的方法
在 Java 項(xiàng)目中處理時(shí)間序列化從來就不是容易的事。一方面要面臨多變的時(shí)間格式,年月日時(shí)分秒,毫秒,納秒,周,還有討厭的時(shí)區(qū),稍不注意就可能收獲一堆異常,另一方面,Java 又提供了 Date 和 LocalDateTime 兩個(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ū)LocalDate和LocalTime與 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 會拋出異常。當(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 格式,就會收獲一個(gè) HttpMessageNotReadableException,JVM 會提示你 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í)都會使用此時(shí)區(qū)
更加幸運(yùn)的是,@JsonFormat 優(yōu)先級比全局配置更高,讓我們可以實(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 序列化,還有一種場景,也會涉及時(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 會忽略時(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)換工具,比如瀏覽器控制臺
- 秒級時(shí)間戳和毫秒時(shí)間戳可能混淆,使用前要約定好
用 long 型時(shí)間戳也不需要考慮序列化問題,大多數(shù)平臺都可以妥善處理 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 就會變成了 "date": 1728572627475,POST 時(shí)也能正確地識別時(shí)間戳。
但是,只有 Date 才有這種優(yōu)渥的待遇,java.time 包的類型仍然面臨自己動手豐衣足食的窘境。
開啟 write-dates-as-timestamps 后,LocalDateTime 等類型會被序列化為整形數(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í)間戳,會丟失精度??傊?,要實(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() 方法,會報(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)成的配置,需要手動實(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 會將所有的接口請求參數(shù)類型統(tǒng)一視為 String,然后調(diào)用 Converter 轉(zhuǎn)換為其他類型。有許多內(nèi)置的 Converter,比如轉(zhuǎn)換為 long 類型時(shí),就使用了內(nèi)置的 StringToNumber 轉(zhuǎn)換類。我們定義的 LongStringToDateConverter 與 StringToNumber 是平級的關(guān)系。
以上是在接口參數(shù)中將 Date、LocalDateTime 轉(zhuǎn)化為時(shí)間戳需要的處理:很簡單,注冊 Converter 即可。
Swagger UI 中的類型
使用 SwaggerUI 時(shí),默認(rèn)會使用 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í)間格式。前者需要手動配置,后者可以使用 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)文章!
- SpringBoot項(xiàng)目中Date類型數(shù)據(jù)在接口返回的時(shí)間不正確的問題解決
- springboot配置請求超時(shí)時(shí)間(Http會話和接口訪問)
- SpringBoot根據(jù)各地區(qū)時(shí)間設(shè)置接口有效時(shí)間的實(shí)現(xiàn)方式
- SpringBoot優(yōu)化接口響應(yīng)時(shí)間的九個(gè)技巧
- Springboot項(xiàng)目長時(shí)間不進(jìn)行接口操作,提示HikariPool-1警告的解決
- SpringBoot接口返回的數(shù)據(jù)時(shí)間與實(shí)際相差8小時(shí)問題排查方式
相關(guān)文章
Jenkins一鍵打包部署SpringBoot應(yīng)用
本文主要介紹了Jenkins一鍵打包部署SpringBoot應(yīng)用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01
Java回調(diào)函數(shù)實(shí)例代碼詳解
這篇文章主要介紹了Java回調(diào)函數(shù)實(shí)例代碼詳解,需要的朋友可以參考下2017-10-10
Mybatis多個(gè)字段模糊匹配同一個(gè)值的案例
這篇文章主要介紹了Mybatis多個(gè)字段模糊匹配同一個(gè)值的案例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
Java實(shí)現(xiàn)ATM系統(tǒng)超全面步驟解讀建議收藏
這篇文章主要為大家詳細(xì)介紹了用Java實(shí)現(xiàn)簡單ATM機(jī)功能,文中實(shí)現(xiàn)流程寫的非常清晰全面,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
Spring實(shí)現(xiàn)定時(shí)任務(wù)的幾種方式總結(jié)
Spring Task 是 Spring 框架提供的一種任務(wù)調(diào)度和異步處理的解決方案,可以按照約定的時(shí)間自動執(zhí)行某個(gè)代碼邏輯它可以幫助開發(fā)者在 Spring 應(yīng)用中輕松地實(shí)現(xiàn)定時(shí)任務(wù)、異步任務(wù)等功能,提高應(yīng)用的效率和可維護(hù)性,需要的朋友可以參考下本文2024-07-07
SpringBoot操作Mongodb的實(shí)現(xiàn)示例
本文主要介紹了SpringBoot操作Mongodb的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06

