詳解springboot接口如何優(yōu)雅的接收時(shí)間類(lèi)型參數(shù)
前言
在上文中我們總結(jié)了前后端Http接口傳參的常用方法,本文主要針對(duì)參數(shù)中的時(shí)間字段如何處理做個(gè)總結(jié),由于時(shí)間的格式有很多種,比較常用的有時(shí)間戳格式、UTC時(shí)間格式、標(biāo)準(zhǔn)時(shí)間格式等,而且時(shí)間參數(shù)出現(xiàn)的位置可能在URL上,可能在Body中,也可能在Header中,所以本文提供一套優(yōu)雅的處理時(shí)間格式字段的解決方案。
時(shí)間格式不做任務(wù)處理會(huì)怎樣?
我們創(chuàng)建一個(gè)簡(jiǎn)單的接口,想通過(guò)@PathVariable
接收Date類(lèi)型的時(shí)間參數(shù),和通過(guò)@RequestParam
接收LocalDateTime類(lèi)型的時(shí)間參數(shù),并且想通過(guò)@RequestBody
來(lái)接收J(rèn)SON中的時(shí)間參數(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測(cè)試請(qǐng)求報(bào)文:
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是不能自動(dòng)幫我們把接口中的時(shí)間參數(shù)轉(zhuǎn)成我們想要的時(shí)間格式的,默認(rèn)是用String接收的,如果直接用LocalDateTime或者Date來(lái)接收會(huì)報(bào)一個(gè)類(lèi)型轉(zhuǎn)換錯(cuò)誤。這個(gè)也是比較好理解的,因?yàn)闀r(shí)間格式太多了,在不知道具體時(shí)間格式的情況下,框架也無(wú)法解析時(shí)間,只能用String接收了,最后將String轉(zhuǎn)成時(shí)間類(lèi)型肯定就報(bào)錯(cuò)了。當(dāng)然我們可以使用String接收,再手動(dòng)轉(zhuǎn)成對(duì)應(yīng)的時(shí)間格式,這個(gè)方法太原始了,接下來(lái)我們看看不同級(jí)別是如何處理時(shí)間字段的。
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í)自動(dòng)將參數(shù)注入到我們的JAVA對(duì)象中是在WebDataBinder
中實(shí)現(xiàn)的,SpringMVC給我們提供了@InitBinder
,可以在接收參數(shù)之前對(duì)參數(shù)解析進(jìn)行初始化設(shè)置,那我們可以在Controller中增加@InitBinder
,然后拿到WebDataBinder
對(duì)象,自定義LocalDateTime和Date兩種CustomEditor這樣我們使用@PathVariable
和@RequestParam
時(shí)就可以自動(dòng)將String轉(zhuǎn)成時(shí)間格式了。但是@RequestBody
默認(rèn)是使用Jackson做JSON數(shù)據(jù)解析的,所以還是不能處理對(duì)象中的時(shí)間格式,我們可以在時(shí)間字段上增加@JsonFormat
注解來(lái)指定時(shí)間格式,從而讓@RequestBody
也可以自動(dòng)解析時(shí)間格式。
@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; }
青銅解析方案存在的問(wèn)題:
@InitBinder
作用域只是當(dāng)前的Controller,如果我用100個(gè)Controller難道我要寫(xiě)100個(gè)@InitBinder
@JsonFormat
也是每個(gè)字段上都要增加個(gè)注解,而且只能支持一種時(shí)間格式,如果我們還要支持時(shí)間戳格式就沒(méi)法做到了。
白銀解決方案
針對(duì)青銅解析方案存在的問(wèn)題1,我們的解決方案是使用@ControllerAdvice
,這樣就不用在每個(gè)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)); } }); } }
針對(duì)青銅方案存在的問(wèn)題2,我們的分析是,既然SpringMvc解析JSON使用的是Jackson ,那么我們就可以讓SpringMVC使用我們自定義的Mapper
來(lái)解析JSON, 我們?cè)?code>@Configuration增加ObjectMapper
, 然后自定義LocalDateTimeSerializer
和LocalDateTimeDeserializer
的序列化的反序處理器,這樣我們就不需要每個(gè)字段都添加上@JsonFormat
了,Jaskson在解析JSON數(shù)據(jù)時(shí)遇到參數(shù)接收類(lèi)型是LocalDateTime類(lèi)型時(shí)會(huì)直接使用我們的自定義處理器,這樣就不會(huì)報(bào)字段轉(zhuǎn)換錯(cuò)誤了,是不是一個(gè)一個(gè)寫(xiě)@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)); } }
存在問(wèn)題:
@ControllerAdvice
基于切面去做攔截,每個(gè)接口都需要經(jīng)過(guò)攔截,性能和優(yōu)雅性不是很好,能不能像Jackson一樣優(yōu)雅的處理呢?
王者解決方案
我們?cè)?code>Configuration中添加Converter<String, LocalDateTime> stringLocalDateTimeConverter()
和Converter<String, Date> stringDateTimeConverter()
,自定義Converter轉(zhuǎn)換時(shí)間類(lèi)型, 這樣不管你是JSON數(shù)據(jù)傳參還是URL傳參數(shù)或是Header傳參,也不管你接收的時(shí)間是類(lèi)型使用Date還是LocalDateTime,更不管你的時(shí)間格式是標(biāo)準(zhǔn)時(shí)間格式還是時(shí)間戳,統(tǒng)統(tǒng)自動(dòng)解決了時(shí)間自動(dòng)接收問(wèn)題,這樣是不是優(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項(xiàng)目開(kāi)發(fā)中如何優(yōu)雅的接收HTTP協(xié)議中的時(shí)間類(lèi)型的參數(shù)。時(shí)間參數(shù)可以出現(xiàn)在URL Path、queryString、FormData、BodyJSON、HTTP header中,時(shí)間格式可以是標(biāo)題格式,時(shí)間戳,接收時(shí)間參數(shù)可以是Date,LocalDateTime,非常優(yōu)雅的全局處理了接口中接口時(shí)間類(lèi)型字段問(wèn)題。
到此這篇關(guān)于詳解springboot接口如何優(yōu)雅的接收時(shí)間類(lèi)型參數(shù)的文章就介紹到這了,更多相關(guān)springboot接收參數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mybatis取別名typeAliases標(biāo)簽的位置放錯(cuò)導(dǎo)致報(bào)錯(cuò)的解決
這篇文章主要介紹了mybatis取別名typeAliases標(biāo)簽的位置放錯(cuò)導(dǎo)致報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09Java 8 lambda表達(dá)式引入詳解及實(shí)例
這篇文章主要介紹了Java 8 lambda表達(dá)式引入詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-05-05springcloud之Feign、ribbon如何設(shè)置超時(shí)時(shí)間和重試機(jī)制
這篇文章主要介紹了springcloud之Feign、ribbon如何設(shè)置超時(shí)時(shí)間和重試機(jī)制,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08Java CGLib動(dòng)態(tài)代理機(jī)制(全面解析)
下面小編就為大家?guī)?lái)一篇Java CGLib動(dòng)態(tài)代理機(jī)制(全面解析)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08實(shí)戰(zhàn)分布式醫(yī)療掛號(hào)系統(tǒng)之設(shè)置微服務(wù)接口開(kāi)發(fā)模塊
這篇文章主要為大家介紹了實(shí)戰(zhàn)分布式醫(yī)療掛號(hào)系統(tǒng)之接口開(kāi)發(fā)醫(yī)院設(shè)置微服務(wù)模塊,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04解析Java中的Field類(lèi)和Method類(lèi)
這篇文章主要介紹了Java中的Field類(lèi)和Method類(lèi),是Java入門(mén)學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-12-12Java發(fā)送郵箱驗(yàn)證碼、session校驗(yàn)功能
本篇主要描述“發(fā)送郵箱驗(yàn)證碼、session校驗(yàn)”相關(guān)前(html\js)后(java)臺(tái)代碼,業(yè)務(wù)邏輯示例,需要的朋友可以參考下2018-02-02