詳解springboot接口如何優(yōu)雅的接收時間類型參數(shù)
前言
在上文中我們總結(jié)了前后端Http接口傳參的常用方法,本文主要針對參數(shù)中的時間字段如何處理做個總結(jié),由于時間的格式有很多種,比較常用的有時間戳格式、UTC時間格式、標(biāo)準(zhǔn)時間格式等,而且時間參數(shù)出現(xiàn)的位置可能在URL上,可能在Body中,也可能在Header中,所以本文提供一套優(yōu)雅的處理時間格式字段的解決方案。
時間格式不做任務(wù)處理會怎樣?
我們創(chuàng)建一個簡單的接口,想通過@PathVariable
接收Date類型的時間參數(shù),和通過@RequestParam
接收LocalDateTime類型的時間參數(shù),并且想通過@RequestBody
來接收J(rèn)SON中的時間參數(shù):
@GetMapping("/time/{today}") public UserDTO time(@PathVariable Date today, @RequestParam LocalDateTime time,@RequestBody UserDTO userDTO) { return userDTO; } @Data public class UserDTO { private Long id; private String userName; private Date now; private Date day; private LocalDateTime time; private LocalDateTime timeStack; }
HTTP測試請求報文:
GET http://localhost:80/time/2023-09-10?time=2023-09-15 11:11:11 Accept: application/json Content-Type: application/json { "id":1, "now":"2023-09-15 13:50:10", "day":"2023-09-15", "time": "2023-09-15 13:50:10", "timeStack": 1694757010407 }
結(jié)果:
如果不做任務(wù)處理,SpringBoot是不能自動幫我們把接口中的時間參數(shù)轉(zhuǎn)成我們想要的時間格式的,默認(rèn)是用String接收的,如果直接用LocalDateTime或者Date來接收會報一個類型轉(zhuǎn)換錯誤。這個也是比較好理解的,因為時間格式太多了,在不知道具體時間格式的情況下,框架也無法解析時間,只能用String接收了,最后將String轉(zhuǎn)成時間類型肯定就報錯了。當(dāng)然我們可以使用String接收,再手動轉(zhuǎn)成對應(yīng)的時間格式,這個方法太原始了,接下來我們看看不同級別是如何處理時間字段的。
Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.time.LocalDateTime';
Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.util.Date';
青銅解決方案
我們知道SpringMVC接收參數(shù)時自動將參數(shù)注入到我們的JAVA對象中是在WebDataBinder
中實現(xiàn)的,SpringMVC給我們提供了@InitBinder
,可以在接收參數(shù)之前對參數(shù)解析進行初始化設(shè)置,那我們可以在Controller中增加@InitBinder
,然后拿到WebDataBinder
對象,自定義LocalDateTime和Date兩種CustomEditor這樣我們使用@PathVariable
和@RequestParam
時就可以自動將String轉(zhuǎn)成時間格式了。但是@RequestBody
默認(rèn)是使用Jackson做JSON數(shù)據(jù)解析的,所以還是不能處理對象中的時間格式,我們可以在時間字段上增加@JsonFormat
注解來指定時間格式,從而讓@RequestBody
也可以自動解析時間格式。
@InitBinder public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport() { @Override public void setAsText(String text) { setValue(DateUtil.parseLocalDateTime(text)); } }); binder.registerCustomEditor(Date.class, new PropertyEditorSupport() { @Override public void setAsText(String text) { setValue(DateUtil.parse(text,NORM_DATETIME_PATTERN,NORM_DATE_PATTERN)); } }); } @Data public class UserDTO { private Long id; private String userName; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date now; @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") private Date day; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private LocalDateTime time; //private LocalDateTime timeStack; }
青銅解析方案存在的問題:
@InitBinder
作用域只是當(dāng)前的Controller,如果我用100個Controller難道我要寫100個@InitBinder
@JsonFormat
也是每個字段上都要增加個注解,而且只能支持一種時間格式,如果我們還要支持時間戳格式就沒法做到了。
白銀解決方案
針對青銅解析方案存在的問題1,我們的解決方案是使用@ControllerAdvice
,這樣就不用在每個Controller是都添加@InitBinder
了
@ControllerAdvice public class GlobalControllerAdvice { @InitBinder public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport() { @Override public void setAsText(String text) { setValue(DateUtil.parseLocalDateTime(text)); } }); binder.registerCustomEditor(Date.class, new PropertyEditorSupport() { @Override public void setAsText(String text) { setValue(DateUtil.parse(text,NORM_DATETIME_PATTERN,NORM_DATE_PATTERN)); } }); } }
針對青銅方案存在的問題2,我們的分析是,既然SpringMvc解析JSON使用的是Jackson ,那么我們就可以讓SpringMVC使用我們自定義的Mapper
來解析JSON, 我們在@Configuration
增加ObjectMapper
, 然后自定義LocalDateTimeSerializer
和LocalDateTimeDeserializer
的序列化的反序處理器,這樣我們就不需要每個字段都添加上@JsonFormat
了,Jaskson在解析JSON數(shù)據(jù)時遇到參數(shù)接收類型是LocalDateTime類型時會直接使用我們的自定義處理器,這樣就不會報字段轉(zhuǎn)換錯誤了,是不是一個一個寫@JsonFormat
優(yōu)雅了許多?
@Configuration public class WebConfig implements WebMvcConfigurer { @Bean @Primary public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); SimpleModule module = new SimpleModule(); module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer()); module.addSerializer(Date.class, new DateTimeSerializer()); module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer()); module.addDeserializer(Date.class, new DateTimeDeserializer()); mapper.registerModule(module); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); return JsonUtils.getMapper(); } } public class DateTimeDeserializer extends StdDeserializer<Date> { public DateTimeDeserializer() { this(null); } public DateTimeDeserializer(Class<?> vc) { super(vc); } @Override public Date deserialize(JsonParser jp, DeserializationContext ctx) throws IOException { String value = jp.getValueAsString(); return DateUtil.parse(value,NORM_DATETIME_PATTERN,NORM_DATE_PATTERN); } } public class DateTimeSerializer extends StdSerializer<Date> { public DateTimeSerializer() { this(null); } public DateTimeSerializer(Class<Date> t) { super(t); } @Override public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider) throws IOException { jgen.writeString(DateUtil.format(value, DatePattern.NORM_DATETIME_PATTERN)); } } public class LocalDateTimeDeserializer extends StdDeserializer<LocalDateTime> { public LocalDateTimeDeserializer() { this(null); } public LocalDateTimeDeserializer(Class<?> vc) { super(vc); } @Override public LocalDateTime deserialize(JsonParser jp, DeserializationContext ctx) throws IOException { String value = jp.getValueAsString(); if (StrUtil.isNumeric(value)) { Date date = new Date(jp.getLongValue()); return LocalDateTime.ofInstant(date.toInstant(), ZoneId.of("Asia/Shanghai")); } else { return DateUtil.parseLocalDateTime(value); } } } public class LocalDateTimeSerializer extends StdSerializer<LocalDateTime> { public LocalDateTimeSerializer() { this(null); } public LocalDateTimeSerializer(Class<LocalDateTime> t) { super(t); } @Override public void serialize(LocalDateTime value, JsonGenerator jgen, SerializerProvider provider) throws IOException { jgen.writeString(LocalDateTimeUtil.formatNormal(value)); } }
存在問題:
@ControllerAdvice
基于切面去做攔截,每個接口都需要經(jīng)過攔截,性能和優(yōu)雅性不是很好,能不能像Jackson一樣優(yōu)雅的處理呢?
王者解決方案
我們在Configuration
中添加Converter<String, LocalDateTime> stringLocalDateTimeConverter()
和Converter<String, Date> stringDateTimeConverter()
,自定義Converter轉(zhuǎn)換時間類型, 這樣不管你是JSON數(shù)據(jù)傳參還是URL傳參數(shù)或是Header傳參,也不管你接收的時間是類型使用Date還是LocalDateTime,更不管你的時間格式是標(biāo)準(zhǔn)時間格式還是時間戳,統(tǒng)統(tǒng)自動解決了時間自動接收問題,這樣是不是優(yōu)雅多了?
@Configuration public class WebConfig { @Bean @Primary public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); SimpleModule module = new SimpleModule(); module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer()); module.addSerializer(Date.class, new DateTimeSerializer()); module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer()); module.addDeserializer(Date.class, new DateTimeDeserializer()); mapper.registerModule(module); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); return JsonUtils.getMapper(); } @Bean public Converter<String, LocalDateTime> stringLocalDateTimeConverter() { return new Converter<String, LocalDateTime>() { @Override public LocalDateTime convert(String source) { if (StrUtil.isNumeric(source)) { return LocalDateTimeUtil.of(Long.parseLong(source)); } else { return DateUtil.parseLocalDateTime(source); } } }; } @Bean public Converter<String, Date> stringDateTimeConverter() { return new Converter<String, Date>() { @Override public Date convert(String source) { if (StrUtil.isNumeric(source)) { return new Date(Long.parseLong(source)); } else { return DateUtil.parse(source); } } }; } }
總結(jié)
本文介紹了在SpringBoot項目開發(fā)中如何優(yōu)雅的接收HTTP協(xié)議中的時間類型的參數(shù)。時間參數(shù)可以出現(xiàn)在URL Path、queryString、FormData、BodyJSON、HTTP header中,時間格式可以是標(biāo)題格式,時間戳,接收時間參數(shù)可以是Date,LocalDateTime,非常優(yōu)雅的全局處理了接口中接口時間類型字段問題。
到此這篇關(guān)于詳解springboot接口如何優(yōu)雅的接收時間類型參數(shù)的文章就介紹到這了,更多相關(guān)springboot接收參數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mybatis取別名typeAliases標(biāo)簽的位置放錯導(dǎo)致報錯的解決
這篇文章主要介紹了mybatis取別名typeAliases標(biāo)簽的位置放錯導(dǎo)致報錯的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09springcloud之Feign、ribbon如何設(shè)置超時時間和重試機制
這篇文章主要介紹了springcloud之Feign、ribbon如何設(shè)置超時時間和重試機制,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08實戰(zhàn)分布式醫(yī)療掛號系統(tǒng)之設(shè)置微服務(wù)接口開發(fā)模塊
這篇文章主要為大家介紹了實戰(zhàn)分布式醫(yī)療掛號系統(tǒng)之接口開發(fā)醫(yī)院設(shè)置微服務(wù)模塊,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-04-04