Spring中WebClient的創(chuàng)建和使用詳解
前言
在Spring5中,出現(xiàn)了Reactive響應(yīng)式編程思想,并且為網(wǎng)絡(luò)編程提供相關(guān)響應(yīng)式編程的支持,如提供了WebFlux,它是Spring提供的異步非阻塞的響應(yīng)式的網(wǎng)絡(luò)框架,相比傳統(tǒng)的SpringMVC框架,可以充分利用多CPU并行處理一些功能,雖然不能提高單個(gè)請(qǐng)求的響應(yīng)能力,但是總體可以提高多核的服務(wù)器性能,提高系統(tǒng)吞吐量和伸縮性,特別適合于IO密集型服務(wù)。
WebClient提供的基于響應(yīng)式的非阻塞的Web請(qǐng)求客戶端,相對(duì)于傳統(tǒng)的RestTemplate,他不阻塞代碼、異步執(zhí)行。
使用WebClient需要引入下面的依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
WebClient的創(chuàng)建
WebClient可以直接通過new來創(chuàng)建,也可以使用構(gòu)造者模式來構(gòu)造。
package com.morris.user.demo; import com.morris.user.entity.Order; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.client.WebClient; import java.util.Arrays; import java.util.concurrent.TimeUnit; /** * WebClient的創(chuàng)建 */ public class WebClientDemo1 { public static void main(String[] args) throws InterruptedException { WebClient webClient = WebClient.create(); webClient.get().uri("http://127.0.0.1:8020/order/findOrderByUserId?userId={userId}", 1).retrieve() .bodyToMono(Order[].class).map(Arrays::asList).subscribe(System.out::println); WebClient webClient2 = WebClient.builder() .baseUrl("http://127.0.0.1:8020") .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .build(); webClient2.get().uri("/order/findOrderByUserId?userId={userId}", 1).retrieve() .bodyToMono(Order[].class).map(Arrays::asList).subscribe(System.out::println); TimeUnit.SECONDS.sleep(5); } }
在應(yīng)用中使用WebClient時(shí)也許你要訪問的URL都來自同一個(gè)應(yīng)用,只是對(duì)應(yīng)不同的URI地址,這個(gè)時(shí)候可以把公用的部分抽出來定義為baseUrl,然后在進(jìn)行WebClient請(qǐng)求的時(shí)候只指定相對(duì)于baseUrl的URL部分即可。這樣的好處是你的baseUrl需要變更的時(shí)候可以只要修改一處即可。
WebClient發(fā)送Get請(qǐng)求
先創(chuàng)建個(gè)webclient.create()實(shí)例,之后調(diào)用get()、post()等調(diào)用方式,uri()指定路徑,retrieve()用來發(fā)起請(qǐng)求并獲得響應(yīng),bodyToFlux(Order.class)用來將請(qǐng)求結(jié)果需要處理為Order數(shù)組,并包裝為Reactor的Flux對(duì)象。
如果返回結(jié)果是一個(gè)JSON字符串,可以使用bodyToMono(),將接收到的JSON字符串轉(zhuǎn)換為對(duì)應(yīng)的對(duì)象。
如果返回結(jié)果是一個(gè)JSON數(shù)組,可以使用bodyToFlux(),將接收到的JSON數(shù)組轉(zhuǎn)換為對(duì)應(yīng)的對(duì)象集合,然后依次處理每一個(gè)元素。
package com.morris.user.demo; import com.morris.user.entity.Order; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.client.WebClient; import java.util.Arrays; import java.util.concurrent.TimeUnit; /** * WebClient發(fā)送Get請(qǐng)求 */ public class WebClientGetDemo { public static void main(String[] args) throws InterruptedException { WebClient webClient = WebClient.create(); webClient.get().uri("http://127.0.0.1:8020/order/findOrderByUserId?userId={userId}", 1).retrieve() .bodyToFlux(Order.class).subscribe(System.out::println);; // 休眠一會(huì),否則WebClient中的線程池還沒執(zhí)行,看不到效果 TimeUnit.SECONDS.sleep(5); } }
WebClient發(fā)送Post請(qǐng)求
可以使用BodyInserters類提供的各種工廠方法來構(gòu)造BodyInserter對(duì)象并將其傳遞給body方法。BodyInserters類包含從Object,Publisher,Resource,F(xiàn)ormData,MultipartData等創(chuàng)建BodyInserter的方法。
package com.morris.user.demo; import com.morris.user.entity.Order; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; /** * WebClient發(fā)送Post請(qǐng)求 */ public class WebClientPostDemo { public static void main(String[] args) { WebClient webClient = WebClient.create(); Order order = new Order(); order.setId(1L); order.setUserId(666L); order.setGoodName("Iphone 13"); order.setPrice(9999); Mono<Long> mono = webClient.post().uri("http://127.0.0.1:8020/order/saveOrder") .body(BodyInserters.fromValue(order)) // .body(Mono.just(order), Order.class) .retrieve() .bodyToMono(Long.class); // 阻塞等待獲取結(jié)果 System.out.println(mono.block()); } }
WebClient對(duì)失敗的處理
package com.morris.user.demo; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClientResponseException; import reactor.core.publisher.Mono; /** * WebClient對(duì)失敗的處理 */ @Slf4j public class WebClientDealFailDemo { public static void main(String[] args) { WebClient webClient = WebClient.create(); WebClient.ResponseSpec responseSpec = webClient.get().uri("http://127.0.0.1:8020/order/error") .retrieve(); Mono<String> mono = responseSpec .onStatus(HttpStatus::is4xxClientError, resp -> { log.error("error4xx:{},msg:{}",resp.statusCode().value(),resp.statusCode().getReasonPhrase()); return Mono.error(new RuntimeException(resp.statusCode().value() + " : " + resp.statusCode().getReasonPhrase())); }) .bodyToMono(String.class) .doOnError(WebClientResponseException.class, err -> { log.info("ERROR status:{},msg:{}",err.getRawStatusCode(),err.getResponseBodyAsString()); throw new RuntimeException(err.getMessage()); }) .onErrorReturn("fallback"); // 阻塞等待獲取結(jié)果 System.out.println(mono.block()); } }
可以使用onStatus根據(jù)響應(yīng)的status code進(jìn)行適配,可以使用doOnError對(duì)異常進(jìn)行適配,可以使用onErrorReturn返回默認(rèn)值。
exchange()
retrieve()方法是直接獲取響應(yīng)body,但是,如果需要響應(yīng)的頭信息、Cookie等,可以使用exchange方法,該方法可以訪問整個(gè)ClientResponse。由于響應(yīng)的得到是異步的,所以都可以調(diào)用block()方法來阻塞當(dāng)前程序,等待獲得響應(yīng)的結(jié)果。
package com.morris.user.demo; import com.morris.user.entity.Order; import org.springframework.web.reactive.function.client.WebClient; import java.util.concurrent.TimeUnit; /** * WebClient使用Exchange發(fā)送請(qǐng)求 */ public class WebClientExchangeDemo { public static void main(String[] args) throws InterruptedException { WebClient webClient = WebClient.create(); webClient.get().uri("http://127.0.0.1:8020/order/findOrderByUserId?userId={userId}", 1) .exchange() .subscribe(r -> { System.out.println(r.headers()); r.bodyToFlux(Order.class).subscribe(System.out::println); }); // 休眠一會(huì),否則WebClient中的線程池還沒執(zhí)行,看不到效果 TimeUnit.SECONDS.sleep(5); } }
filter
WebClient也提供了Filter,對(duì)應(yīng)于org.springframework.web.reactive.function.client.ExchangeFilterFunction接口,可以攔截request,也可以攔截response。
package com.morris.user.demo; import com.morris.user.entity.Order; import lombok.extern.slf4j.Slf4j; import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; import java.util.concurrent.TimeUnit; /** * WebClient使用filter攔截器 */ @Slf4j public class WebClientFilterDemo { private static ExchangeFilterFunction logResponseStatus() { return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> { log.info("Response Status {}", clientResponse.statusCode()); return Mono.just(clientResponse); }); } public static void main(String[] args) throws InterruptedException { WebClient webClient = WebClient.builder().filter(logResponseStatus()).build(); webClient.get().uri("http://127.0.0.1:8020/order/findOrderByUserId?userId={userId}", 1) .exchange() .subscribe(r -> { System.out.println(r.headers()); r.bodyToFlux(Order.class).subscribe(System.out::println); }); // 休眠一會(huì),否則WebClient中的線程池還沒執(zhí)行,看不到效果 TimeUnit.SECONDS.sleep(5); } }
Attributes
可以使用attribute在多個(gè)filter之間傳遞參數(shù)。
package com.morris.user.demo; import com.morris.user.entity.Order; import lombok.extern.slf4j.Slf4j; import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; import java.util.Optional; import java.util.concurrent.TimeUnit; /** * WebClient使用attribute傳遞參數(shù) */ @Slf4j public class WebClientAttributesDemo { private static ExchangeFilterFunction filterRequest() { return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> { Optional<Object> myAttribute = clientRequest.attribute("myAttribute"); System.out.println(myAttribute.get()); return Mono.just(clientRequest); }); } public static void main(String[] args) throws InterruptedException { WebClient webClient = WebClient.builder().filter(filterRequest()).build(); webClient.get().uri("http://127.0.0.1:8020/order/findOrderByUserId?userId={userId}", 1) .attribute("myAttribute", "myAttribute") .exchange() .subscribe(r -> { System.out.println(r.headers()); r.bodyToFlux(Order.class).subscribe(System.out::println); }); // 休眠一會(huì),否則WebClient中的線程池還沒執(zhí)行,看不到效果 TimeUnit.SECONDS.sleep(5); } }
到此這篇關(guān)于Spring中WebClient的創(chuàng)建和使用詳解的文章就介紹到這了,更多相關(guān)WebClient的創(chuàng)建和使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot中useGeneratedKeys用法小結(jié)
本文主要介紹了Springboot中useGeneratedKeys用法小結(jié),useGeneratedKeys?是 MyBatis 框架中的一個(gè)參數(shù),用于指定是否允許 JDBC 支持自動(dòng)生成主鍵,感興趣的可以了解一下2024-09-09Java16 JDK安裝并設(shè)置環(huán)境變量的方法步驟
突然想起自己大學(xué)剛接觸java的時(shí)候,要下載JDK和配置環(huán)境變量,那時(shí)候我上網(wǎng)找了很多教學(xué),本文就詳細(xì)的介紹一下Java16 JDK安裝并設(shè)置環(huán)境變量,感興趣的可以了解一下2021-09-09Java實(shí)戰(zhàn)之仿天貓商城系統(tǒng)的實(shí)現(xiàn)
這篇文章主要介紹了如何利用Java制作一個(gè)基于SSM框架的迷你天貓商城系統(tǒng),文中采用的技術(shù)有JSP、Springboot、SpringMVC、Spring等,需要的可以參考一下2022-03-03深入淺析springsecurity入門登錄授權(quán)
SpringSecurity為我們提供了基于注解的權(quán)限控制方案,這也是我們項(xiàng)目中主要采用的方式,我們可以使用注解去指定訪問對(duì)應(yīng)的資源所需的權(quán)限,這篇文章主要介紹了springsecurity入門登錄授權(quán),需要的朋友可以參考下2024-05-05關(guān)于maven項(xiàng)目中使用BCrypt加密方式
BCrypt是一種基于Blowfish加密算法的密碼散列函數(shù),用于安全存儲(chǔ)和驗(yàn)證用戶密碼,它通過引入鹽和工作因子增加計(jì)算復(fù)雜度,有效防止彩虹表攻擊和破解,BCrypt具備適應(yīng)性工作因子、成本參數(shù)調(diào)整、迭代哈希和密鑰擴(kuò)展等特點(diǎn),被廣泛應(yīng)用于Web應(yīng)用程序的安全性設(shè)計(jì)中2024-10-10jackson 如何將實(shí)體轉(zhuǎn)json json字符串轉(zhuǎn)實(shí)體
這篇文章主要介紹了jackson 實(shí)現(xiàn)將實(shí)體轉(zhuǎn)json json字符串轉(zhuǎn)實(shí)體,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10Springboot并發(fā)調(diào)優(yōu)之大事務(wù)和長(zhǎng)連接
這篇文章主要介紹了Springboot并發(fā)調(diào)優(yōu)之大事務(wù)和長(zhǎng)連接,重點(diǎn)分享長(zhǎng)事務(wù)以及長(zhǎng)連接導(dǎo)致的并發(fā)排查和優(yōu)化思路和示例,具有一定的參考價(jià)值,感興趣的可以了解一下2022-05-05Springboot實(shí)現(xiàn)多文件上傳代碼解析
這篇文章主要介紹了Springboot實(shí)現(xiàn)多文件上傳代碼解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04解決IDEA2020.1.2IDEA打不開的問題(最新分享)
由于idea安裝多了某個(gè)jar,點(diǎn)擊出現(xiàn)讀條后閃退情況,接下來通過本文給大家分享解決IDEA2020.1.2IDEA打不開的問題,非常不錯(cuò),具有一定的參考借鑒價(jià)值,感興趣的朋友跟隨小編一起看看吧2020-07-07Spring AI內(nèi)置DeepSeek的詳細(xì)步驟
Spring AI 最新快照版已經(jīng)內(nèi)置 DeepSeek 了,所以以后項(xiàng)目中對(duì)接 DeepSeek 就方便多了,但因?yàn)榭煺瞻鏁?huì)有很多 Bug,所以今天咱們就來看穩(wěn)定版的 Spring AI 如何對(duì)接 DeepSeek 滿血版,感興趣的小伙伴跟著小編一起來看看吧2025-02-02