Spring WebClient和Java日期时间字段
WebClient是Spring Framework的反應式客戶端,用于進行服務到服務的調用。
WebClient已成為我的實用工具,但是最近我意外地遇到了一個問題,即它處理Java 8時間字段的方式使我絆倒了,本文對此進行了詳細介紹。
快樂之路
首先是幸福的道路。 使用WebClient時, Spring Boot建議將“ WebClient.Builder”注入到類中,而不是“ WebClient”本身,并且已經自動配置了WebClient.Builder并可以注入。
考慮一個虛擬的“城市”域和一個創建“城市”的客戶。 “城市”具有簡單的結構,請注意creationDate是Java8“即時”類型:
import java.time.Instant data class City( val id: Long, val name: String, val country: String, val pop: Long, val creationDate: Instant = Instant.now() )用于創建此類型實例的客戶端如下所示:
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) } } }了解如何以一種流暢的方式表達意圖。 首先設置uri和標頭,然后放置請求主體,然后將響應解組回“ City”響應類型。
一切都很好。 現在測試看起來如何。
我正在使用出色的Wiremock來啟動虛擬遠程服務,并使用此CitiesClient發送請求,方法如下:
@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() } } }在突出顯示的行中,我要確保遠程服務以ISO-8601格式接收日期為“ 1985-02-01T10:10:10Z”。 在這種情況下,一切正常進行,測試通過了。
不太開心的路
現在考慮以某種形式自定義WebClient.Builder的情況。 一個例子是說我正在使用注冊表服務,并且我想通過此注冊表查找遠程服務,然后打電話,然后必須自定義WebClient以在其上添加“ @LoadBalanced”注釋- 這里有一些詳細信息
可以這么說,我以這種方式自定義了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>() } }它看起來很簡單,但是現在以前的測試失敗了。 具體來說,網上的creationDate的日期格式不再是ISO-8601,原始請求如下所示:
{ "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" }查看日期格式有何不同。
問題
這個問題的根本原因很簡單,Spring Boot在WebClient.Builder上添加了一堆配置,當我自己明確創建bean時,這些配置會丟失。 特別是在這種情況下,在后臺創建了一個Jackson ObjectMapper,默認情況下將日期寫為時間戳– 此處有一些詳細信息。
解
好的,那么我們如何取回Spring Boot進行的自定義。 我實質上已經在Spring中復制了稱為“ WebClientAutoConfiguration”的自動配置的行為,它看起來像這樣:
@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>() } }除了復制這種行為,可能還有更好的方法,但是這種方法對我有用。
現在發布的內容如下所示:
{ "id" : 1 , "name" : "some city" , "country" : "some country" , "pop" : 1000 , "creationDate" : "1985-02-01T10:10:10Z" }日期以正確的格式顯示。
結論
Spring Boot對WebClient的自動配置提供了一套明確的默認值。 如果出于任何原因需要顯式配置WebClient及其構建器,請警惕Spring Boot添加的一些自定義項并將其復制為自定義bean。 在我的案例中,我的自定義“ WebClient.Builder”中缺少針對Java 8日期的Jackson定制,因此必須明確說明。
此處提供示例測試和自定義
翻譯自: https://www.javacodegeeks.com/2020/01/spring-webclient-and-java-date-time-fields.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的Spring WebClient和Java日期时间字段的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Boot中的高级配置文件管
- 下一篇: 在Java中将时间单位转换为持续时间