Spring WebClient和Java日期时间字段
WebClient是Spring Framework的反應(yīng)式客戶(hù)端,用于進(jìn)行服務(wù)到服務(wù)的調(diào)用。
WebClient已成為我的實(shí)用工具,但是最近我意外地遇到了一個(gè)問(wèn)題,即它處理Java 8時(shí)間字段的方式使我絆倒了,本文對(duì)此進(jìn)行了詳細(xì)介紹。
快樂(lè)之路
首先是幸福的道路。 使用WebClient時(shí), Spring Boot建議將“ WebClient.Builder”注入到類(lèi)中,而不是“ WebClient”本身,并且已經(jīng)自動(dòng)配置了WebClient.Builder并可以注入。
考慮一個(gè)虛擬的“城市”域和一個(gè)創(chuàng)建“城市”的客戶(hù)。 “城市”具有簡(jiǎn)單的結(jié)構(gòu),請(qǐng)注意creationDate是Java8“即時(shí)”類(lèi)型:
import java.time.Instant data class City( val id: Long, val name: String, val country: String, val pop: Long, val creationDate: Instant = Instant.now() )用于創(chuàng)建此類(lèi)型實(shí)例的客戶(hù)端如下所示:
CitiesClient( class CitiesClient( private val webClientBuilder: WebClient.Builder, private val citiesBaseUrl: String ) { fun createCity(city: City): Mono<City> { val uri: URI = UriComponentsBuilder .fromUriString(citiesBaseUrl) .path( "/cities" ) .build() .encode() .toUri() val webClient: WebClient = this .webClientBuilder.build() return webClient.post() .uri(uri) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .bodyValue(city) .exchange() .flatMap { clientResponse -> clientResponse.bodyToMono(City:: class .java) } } }了解如何以一種流暢的方式表達(dá)意圖。 首先設(shè)置uri和標(biāo)頭,然后放置請(qǐng)求主體,然后將響應(yīng)解組回“ City”響應(yīng)類(lèi)型。
一切都很好。 現(xiàn)在測(cè)試看起來(lái)如何。
我正在使用出色的Wiremock來(lái)啟動(dòng)虛擬遠(yuǎn)程服務(wù),并使用此CitiesClient發(fā)送請(qǐng)求,方法如下:
@SpringBootTest @AutoConfigureJson WebClientConfigurationTest { class WebClientConfigurationTest { @Autowired private lateinit var webClientBuilder: WebClient.Builder @Autowired private lateinit var objectMapper: ObjectMapper @Test fun testAPost() { val dateAsString = "1985-02-01T10:10:10Z" val city = City( id = 1L, name = "some city" , country = "some country" , pop = 1000L, creationDate = Instant.parse(dateAsString) ) WIREMOCK_SERVER.stubFor( post(urlMatching( "/cities" )) .withHeader( "Accept" , equalTo( "application/json" )) .withHeader( "Content-Type" , equalTo( "application/json" )) .willReturn( aResponse() .withHeader( "Content-Type" , "application/json" ) .withStatus(HttpStatus.CREATED.value()) .withBody(objectMapper.writeValueAsString(city)) ) ) val citiesClient = CitiesClient(webClientBuilder, " http://localhost: ${WIREMOCK_SERVER.port()}" ) val citiesMono: Mono<City> = citiesClient.createCity(city) StepVerifier .create(citiesMono) .expectNext(city) .expectComplete() .verify() //Ensure that date field is in ISO-8601 format.. WIREMOCK_SERVER.verify( postRequestedFor(urlPathMatching( "/cities" )) .withRequestBody(matchingJsonPath( "$.creationDate" , equalTo(dateAsString))) ) } companion object { private val WIREMOCK_SERVER = WireMockServer(WireMockConfiguration.wireMockConfig().dynamicPort().notifier(ConsoleNotifier( true ))) @BeforeAll @JvmStatic fun beforeAll() { WIREMOCK_SERVER.start() } @AfterAll @JvmStatic fun afterAll() { WIREMOCK_SERVER.stop() } } }在突出顯示的行中,我要確保遠(yuǎn)程服務(wù)以ISO-8601格式接收日期為“ 1985-02-01T10:10:10Z”。 在這種情況下,一切正常進(jìn)行,測(cè)試通過(guò)了。
不太開(kāi)心的路
現(xiàn)在考慮以某種形式自定義WebClient.Builder的情況。 一個(gè)例子是說(shuō)我正在使用注冊(cè)表服務(wù),并且我想通過(guò)此注冊(cè)表查找遠(yuǎn)程服務(wù),然后打電話,然后必須自定義WebClient以在其上添加“ @LoadBalanced”注釋- 這里有一些詳細(xì)信息
可以這么說(shuō),我以這種方式自定義了WebClient.Builder:
@Configuration WebClientConfiguration { class WebClientConfiguration { @Bean fun webClientBuilder(): WebClient.Builder { return WebClient.builder().filter { req, next -> LOGGER.error( "Custom filter invoked.." ) next.exchange(req) } } companion object { val LOGGER = loggerFor<WebClientConfiguration>() } }它看起來(lái)很簡(jiǎn)單,但是現(xiàn)在以前的測(cè)試失敗了。 具體來(lái)說(shuō),網(wǎng)上的creationDate的日期格式不再是ISO-8601,原始請(qǐng)求如下所示:
{ "id" : 1 , "name" : "some city" , "country" : "some country" , "pop" : 1000 , "creationDate" : 476100610.000000000 }與工作要求:
{ "id" : 1 , "name" : "some city" , "country" : "some country" , "pop" : 1000 , "creationDate" : "1985-02-01T10:10:10Z" }查看日期格式有何不同。
問(wèn)題
這個(gè)問(wèn)題的根本原因很簡(jiǎn)單,Spring Boot在WebClient.Builder上添加了一堆配置,當(dāng)我自己明確創(chuàng)建bean時(shí),這些配置會(huì)丟失。 特別是在這種情況下,在后臺(tái)創(chuàng)建了一個(gè)Jackson ObjectMapper,默認(rèn)情況下將日期寫(xiě)為時(shí)間戳– 此處有一些詳細(xì)信息。
解
好的,那么我們?nèi)绾稳』豐pring Boot進(jìn)行的自定義。 我實(shí)質(zhì)上已經(jīng)在Spring中復(fù)制了稱(chēng)為“ WebClientAutoConfiguration”的自動(dòng)配置的行為,它看起來(lái)像這樣:
@Configuration WebClientConfiguration { class WebClientConfiguration { @Bean fun webClientBuilder(customizerProvider: ObjectProvider<WebClientCustomizer>): WebClient.Builder { val webClientBuilder: WebClient.Builder = WebClient .builder() .filter { req, next -> LOGGER.error( "Custom filter invoked.." ) next.exchange(req) } customizerProvider.orderedStream() .forEach { customizer -> customizer.customize(webClientBuilder) } return webClientBuilder; } companion object { val LOGGER = loggerFor<WebClientConfiguration>() } }除了復(fù)制這種行為,可能還有更好的方法,但是這種方法對(duì)我有用。
現(xiàn)在發(fā)布的內(nèi)容如下所示:
{ "id" : 1 , "name" : "some city" , "country" : "some country" , "pop" : 1000 , "creationDate" : "1985-02-01T10:10:10Z" }日期以正確的格式顯示。
結(jié)論
Spring Boot對(duì)WebClient的自動(dòng)配置提供了一套明確的默認(rèn)值。 如果出于任何原因需要顯式配置WebClient及其構(gòu)建器,請(qǐng)警惕Spring Boot添加的一些自定義項(xiàng)并將其復(fù)制為自定義bean。 在我的案例中,我的自定義“ WebClient.Builder”中缺少針對(duì)Java 8日期的Jackson定制,因此必須明確說(shuō)明。
此處提供示例測(cè)試和自定義
翻譯自: https://www.javacodegeeks.com/2020/01/spring-webclient-and-java-date-time-fields.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的Spring WebClient和Java日期时间字段的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Spring Boot中的高级配置文件管
- 下一篇: 在Java中将时间单位转换为持续时间