Spring中網(wǎng)絡(luò)請求客戶端WebClient的使用詳解
在 Spring 5 之前,如果我們想要調(diào)用其他系統(tǒng)提供的 HTTP 服務(wù),通常可以使用 Spring 提供的 RestTemplate 來訪問,不過由于 RestTemplate 是 Spring 3 中引入的同步阻塞式 HTTP 客戶端,因此存在一定性能瓶頸。根據(jù) Spring 官方文檔介紹,在將來的版本中它可能會(huì)被棄用。
? 作為替代,Spring 官方已在 Spring 5 中引入了 WebClient 作為非阻塞式 Reactive HTTP 客戶端。下面通過樣例演示如何使用 WebClient。
一、基本介紹
1.什么是 WebClient
從 Spring 5 開始,Spring 中全面引入了 Reactive 響應(yīng)式編程。而 WebClient 則是 Spring WebFlux 模塊提供的一個(gè)非阻塞的基于響應(yīng)式編程的進(jìn)行 Http 請求的客戶端工具。
由于 WebClient 的請求模式屬于異步非阻塞,能夠以少量固定的線程處理高并發(fā)的 HTTP 請求。因此,從 Spring 5 開始,HTTP 服務(wù)之間的通信我們就可以考慮使用 WebClient 來取代之前的 RestTemplate。
2.WebClient 的優(yōu)勢
(1)與 RestTemplate 相比,WebClient 有如下優(yōu)勢:
- 非阻塞,Reactive 的,并支持更高的并發(fā)性和更少的硬件資源。
- 提供利用 Java 8 lambdas 的函數(shù) API。
- 支持同步和異步方案。
- 支持從服務(wù)器向上或向下流式傳輸。
(2)RestTemplate 不適合在非阻塞應(yīng)用程序中使用,因此 Spring WebFlux 應(yīng)用程序應(yīng)始終使用 WebClient。在大多數(shù)高并發(fā)場景中,WebClient 也應(yīng)該是 Spring MVC 中的首選,并且用于編寫一系列遠(yuǎn)程,相互依賴的調(diào)用。
3.安裝配置
編輯 pom.xml 文件,添加 Spring WebFlux 依賴,從而可以使用 WebClient。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
二、創(chuàng)建 WebClient 實(shí)例
? 從 WebClient 的源碼中可以看出,WebClient 接口提供了三個(gè)不同的靜態(tài)方法來創(chuàng)建 WebClient 實(shí)例:
1.利用 create() 創(chuàng)建
(1)下面利用 create() 方法創(chuàng)建一個(gè) WebClient 對象,并利用該對象請求一個(gè)網(wǎng)絡(luò)接口,最后將結(jié)果以字符串的形式打印出來。
注意:由于利用 create() 創(chuàng)建的 WebClient 對象沒有設(shè)定 baseURL,所以這里的 uri() 方法相當(dāng)于重寫 baseURL。
WebClient webClient = WebClient.create(); Mono<String> mono = webClient .get() // GET 請求 .uri("http://jsonplaceholder.typicode.com/posts/1") // 請求路徑 .retrieve() // 獲取響應(yīng)體 .bodyToMono(String.class); //響應(yīng)數(shù)據(jù)類型轉(zhuǎn)換 System.out.println(mono.block());
2.利用 create(String baseUrl) 創(chuàng)建
(1)下面利用 create(String baseUrl) 方法創(chuàng)建一個(gè) WebClient 對象,并利用該對象請求一個(gè)網(wǎng)絡(luò)接口,最后將結(jié)果以字符串的形式打印出來。
注意:由于利用 create(String baseUrl) 創(chuàng)建的 WebClient 對象時(shí)已經(jīng)設(shè)定了 baseURL,所以 uri() 方法會(huì)將返回的結(jié)果和 baseUrl 進(jìn)行拼接組成最終需要遠(yuǎn)程請求的資源 URL。
WebClient webClient = WebClient.create("http://jsonplaceholder.typicode.com"); Mono<String> mono = webClient .get() // GET 請求 .uri("/posts/1") // 請求路徑 .retrieve() // 獲取響應(yīng)體 .bodyToMono(String.class); //響應(yīng)數(shù)據(jù)類型轉(zhuǎn)換 System.out.println(mono.block());
3.利用 builder 創(chuàng)建(推薦)
(1)下面使用 builder() 返回一個(gè) WebClient.Builder,然后再調(diào)用 build 就可以返回 WebClient 對象。并利用該對象請求一個(gè)網(wǎng)絡(luò)接口,最后將結(jié)果以字符串的形式打印出來。
注意:由于返回的不是 WebClient 類型而是 WebClient.Builder,我們可以通過返回的 WebClient.Builder 設(shè)置一些配置參數(shù)(例如:baseUrl、header、cookie 等),然后再調(diào)用 build 就可以返回 WebClient 對象了
WebClient webClient = WebClient.builder() .baseUrl("http://jsonplaceholder.typicode.com") .defaultHeader(HttpHeaders.USER_AGENT,"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)") .defaultCookie("ACCESS_TOKEN", "test_token") .build(); Mono<String> mono = webClient .get() // GET 請求 .uri("/posts/1") // 請求路徑 .retrieve() // 獲取響應(yīng)體 .bodyToMono(String.class); //響應(yīng)數(shù)據(jù)類型轉(zhuǎn)換 System.out.println(mono.block());
三、GET 請求
1.獲取 String 結(jié)果數(shù)據(jù)
下面代碼將響應(yīng)結(jié)果映射為一個(gè) String 字符串,并打印出來。
@RestController public class HelloController { // 創(chuàng)建 WebClient 對象 private WebClient webClient = WebClient.builder() .baseUrl("http://jsonplaceholder.typicode.com") .build(); @GetMapping("/test") public void test() { Mono<String> mono = webClient .get() // GET 請求 .uri("/posts/1") // 請求路徑 .retrieve() // 獲取響應(yīng)體 .bodyToMono(String.class); //響應(yīng)數(shù)據(jù)類型轉(zhuǎn)換 System.out.println(mono.block()); return; } }
2.將結(jié)果轉(zhuǎn)換為對象
(1)當(dāng)響應(yīng)的結(jié)果是 JSON 時(shí),也可以直接指定為一個(gè) Object,WebClient 將接收到響應(yīng)后把 JSON 字符串轉(zhuǎn)換為對應(yīng)的對象。
@RestController public class HelloController { // 創(chuàng)建 WebClient 對象 private WebClient webClient = WebClient.builder() .baseUrl("http://jsonplaceholder.typicode.com") .build(); @GetMapping("/test") public void test() { Mono<PostBean> mono = webClient .get() // GET 請求 .uri("/posts/1") // 請求路徑 .retrieve() // 獲取響應(yīng)體 .bodyToMono(PostBean.class); //響應(yīng)數(shù)據(jù)類型轉(zhuǎn)換 System.out.println(mono.block()); return; } }
(2)其中定義的實(shí)體 Bean 代碼如下:
@Getter @Setter @ToString public class PostBean { private int userId; private int id; private String title; private String body; }
3.將結(jié)果轉(zhuǎn)成集合
(1)假設(shè)接口返回的是一個(gè) json 數(shù)組,內(nèi)容如下:
(2)我們也可以將其轉(zhuǎn)成對應(yīng)的 Bean 集合:
@RestController public class HelloController { // 創(chuàng)建 WebClient 對象 private WebClient webClient = WebClient.builder() .baseUrl("http://jsonplaceholder.typicode.com") .build(); @GetMapping("/test") public void test() { Flux<PostBean> flux = webClient .get() // GET 請求 .uri("/posts") // 請求路徑 .retrieve() // 獲取響應(yīng)體 .bodyToFlux(PostBean.class); //響應(yīng)數(shù)據(jù)類型轉(zhuǎn)換 List<PostBean> posts = flux.collectList().block(); System.out.println("結(jié)果數(shù):" + posts.size()); return; } }
4.參數(shù)傳遞的幾種方式
下面 3 種方式的結(jié)果都是一樣的。
(1)使用占位符的形式傳遞參數(shù):
Mono<String> mono = webClient .get() // GET 請求 .uri("/{1}/{2}", "posts", "1") // 請求路徑 .retrieve() // 獲取響應(yīng)體 .bodyToMono(String.class); //響應(yīng)數(shù)據(jù)類型轉(zhuǎn)換
(2)另一種使用占位符的形式:
String type = "posts"; int id = 1; Mono<String> mono = webClient .get() // GET 請求 .uri("/{type}/{id}", type, id) // 請求路徑 .retrieve() // 獲取響應(yīng)體 .bodyToMono(String.class); //響應(yīng)數(shù)據(jù)類型轉(zhuǎn)換 System.out.println(mono.block());
(3)我們也可以使用 map 裝載參數(shù):
Map<String,Object> map = new HashMap<>(); map.put("type", "posts"); map.put("id", 1); Mono<String> mono = webClient .get() // GET 請求 .uri("/{type}/{id}", map) // 請求路徑 .retrieve() // 獲取響應(yīng)體 .bodyToMono(String.class); //響應(yīng)數(shù)據(jù)類型轉(zhuǎn)換
5.subscribe 訂閱(非阻塞式調(diào)用)
(1)前面的樣例我們都是人為地使用 block 方法來阻塞當(dāng)前程序。其實(shí) WebClient 是異步的,也就是說等待響應(yīng)的同時(shí)不會(huì)阻塞正在執(zhí)行的線程。只有在響應(yīng)結(jié)果準(zhǔn)備就緒時(shí),才會(huì)發(fā)起通知。
@RestController public class HelloController { // 創(chuàng)建 WebClient 對象 private WebClient webClient = WebClient.builder() .baseUrl("http://jsonplaceholder.typicode.com") .build(); @GetMapping("/test") public void test() { System.out.println("--- begin ---"); Mono<String> mono = webClient .get() // GET 請求 .uri("/posts/1") // 請求路徑 .retrieve() // 獲取響應(yīng)體 .bodyToMono(String.class); //響應(yīng)數(shù)據(jù)類型轉(zhuǎn)換 // 訂閱(異步處理結(jié)果) mono.subscribe(result -> { System.out.println(result); }); System.out.println("--- end ---"); return; } }
附:使用 exchange() 方法獲取完整的響應(yīng)內(nèi)容
1.方法介紹
(1)前面我們都是使用 retrieve() 方法直接獲取到了響應(yīng)的內(nèi)容,如果我們想獲取到響應(yīng)的頭信息、Cookie 等,可以在通過 WebClient 請求時(shí)把調(diào)用 retrieve() 改為調(diào)用 exchange()。
(2)通過 exchange() 方法可以訪問到代表響應(yīng)結(jié)果的對象,通過該對象我們可以獲取響應(yīng)碼、contentType、contentLength、響應(yīng)消息體等。
2.使用樣例
下面代碼請求一個(gè)網(wǎng)絡(luò)接口,并將響應(yīng)體、響應(yīng)頭、響應(yīng)碼打印出來。其中響應(yīng)體的類型設(shè)置為 String。
@RestController public class HelloController { // 創(chuàng)建 WebClient 對象 private WebClient webClient = WebClient.builder() .baseUrl("http://jsonplaceholder.typicode.com") .build(); @GetMapping("/test") public void test() { Mono<ClientResponse> mono = webClient .get() // GET 請求 .uri("/posts/1") // 請求路徑 .exchange(); // 獲取完整的響應(yīng)對象 ClientResponse response = mono.block(); HttpStatus statusCode = response.statusCode(); // 獲取響應(yīng)碼 int statusCodeValue = response.rawStatusCode(); // 獲取響應(yīng)碼值 Headers headers = response.headers(); // 獲取響應(yīng)頭 // 獲取響應(yīng)體 Mono<String> resultMono = response.bodyToMono(String.class); String body = resultMono.block(); // 輸出結(jié)果 System.out.println("statusCode:" + statusCode); System.out.println("statusCodeValue:" + statusCodeValue); System.out.println("headers:" + headers.asHttpHeaders()); System.out.println("body:" + body); return; } }
四、POST 請求
1.發(fā)送一個(gè) JSON 格式數(shù)據(jù)(使用 json 字符串)
(1)下面代碼使用 post 方式發(fā)送一個(gè) json 格式的字符串,并將結(jié)果打印出來(以字符串的形式)。
@RestController public class HelloController { // 創(chuàng)建 WebClient 對象 private WebClient webClient = WebClient.builder() .baseUrl("http://jsonplaceholder.typicode.com") .build(); @GetMapping("/test") public void test() { // 需要提交的 json 字符串 String jsonStr = "{\"userId\": 222,\"title\": \"abc\",\"body\": \"航歌\"}"; // 發(fā)送請求 Mono<String> mono = webClient .post() // POST 請求 .uri("/posts") // 請求路徑 .contentType(MediaType.APPLICATION_JSON_UTF8) .body(BodyInserters.fromObject(jsonStr)) .retrieve() // 獲取響應(yīng)體 .bodyToMono(String.class); //響應(yīng)數(shù)據(jù)類型轉(zhuǎn)換 // 輸出結(jié)果 System.out.println(mono.block()); return; } }
2.發(fā)送一個(gè) JSON 格式數(shù)據(jù)(使用 Java Bean)
(1)下面代碼使用 post 方式發(fā)送一個(gè) Bean 對象,并將結(jié)果打印出來(以字符串的形式)。結(jié)果同上面是一樣的:
@RestController public class HelloController { // 創(chuàng)建 WebClient 對象 private WebClient webClient = WebClient.builder() .baseUrl("http://jsonplaceholder.typicode.com") .build(); @GetMapping("/test") public void test() { // 要發(fā)送的數(shù)據(jù)對象 PostBean postBean = new PostBean(); postBean.setUserId(222); postBean.setTitle("abc"); postBean.setBody("航歌"); // 發(fā)送請求 Mono<String> mono = webClient .post() // POST 請求 .uri("/posts") // 請求路徑 .contentType(MediaType.APPLICATION_JSON_UTF8) .syncBody(postBean) .retrieve() // 獲取響應(yīng)體 .bodyToMono(String.class); //響應(yīng)數(shù)據(jù)類型轉(zhuǎn)換 // 輸出結(jié)果 System.out.println(mono.block()); return; } }
(2)上面發(fā)送的 Bean 對象實(shí)際上會(huì)轉(zhuǎn)成如下格式的 JSON 數(shù)據(jù)提交:
3.使用 Form 表單的形式提交數(shù)據(jù)
(1)下面樣例使用 POST 方式發(fā)送 multipart/form-data 格式的數(shù)據(jù):
@RestController public class HelloController { // 創(chuàng)建 WebClient 對象 private WebClient webClient = WebClient.builder() .baseUrl("http://jsonplaceholder.typicode.com") .build(); @GetMapping("/test") public void test() { //提交參數(shù)設(shè)置 MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); map.add("title", "abc"); map.add("body", "航歌"); // 發(fā)送請求 Mono<String> mono = webClient .post() // POST 請求 .uri("/posts") // 請求路徑 .contentType(MediaType.APPLICATION_FORM_URLENCODED) .body(BodyInserters.fromFormData(map)) .retrieve() // 獲取響應(yīng)體 .bodyToMono(String.class); //響應(yīng)數(shù)據(jù)類型轉(zhuǎn)換 // 輸出結(jié)果 System.out.println(mono.block()); return; } }
(2)上面代碼最終會(huì)通過如下這種 form 表單方式提交數(shù)據(jù):
4.將結(jié)果轉(zhuǎn)成自定義對象
? 上面樣例我們都是將響應(yīng)結(jié)果以 String 形式接收,其實(shí) WebClient 還可以自動(dòng)將響應(yīng)結(jié)果轉(zhuǎn)成自定的對象或則數(shù)組。具體可以參考前面寫的文章:
5.設(shè)置 url 參數(shù)
(1)如果 url 地址上面需要傳遞一些參數(shù),可以使用占位符的方式:
String url = "http://jsonplaceholder.typicode.com/{1}/{2}"; String url = "http://jsonplaceholder.typicode.com/{type}/{id}";
(2)具體的用法可以參考前面寫的文章:
6.subscribe 訂閱(非阻塞式調(diào)用)
(1)前面的樣例我們都是人為地使用 block 方法來阻塞當(dāng)前程序。其實(shí) WebClient 是異步的,也就是說等待響應(yīng)的同時(shí)不會(huì)阻塞正在執(zhí)行的線程。只有在響應(yīng)結(jié)果準(zhǔn)備就緒時(shí),才會(huì)發(fā)起通知。
@RestController public class HelloController { // 創(chuàng)建 WebClient 對象 private WebClient webClient = WebClient.builder() .baseUrl("http://jsonplaceholder.typicode.com") .build(); @GetMapping("/test") public void test() { System.out.println("--- begin ---"); // 需要提交的 json 字符串 String jsonStr = "{\"userId\": 222,\"title\": \"abc\",\"body\": \"航歌\"}"; Mono<String> mono = webClient .post() // POST 請求 .uri("/posts") // 請求路徑 .contentType(MediaType.APPLICATION_JSON_UTF8) .body(BodyInserters.fromObject(jsonStr)) .retrieve() // 獲取響應(yīng)體 .bodyToMono(String.class); //響應(yīng)數(shù)據(jù)類型轉(zhuǎn)換 // 訂閱(異步處理結(jié)果) mono.subscribe(result -> { System.out.println(result); }); System.out.println("--- end ---"); return; } }
以上就是Spring中網(wǎng)絡(luò)請求客戶端WebClient的使用詳解的詳細(xì)內(nèi)容,更多關(guān)于Spring WebClient的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Springboot通過lucene實(shí)現(xiàn)全文檢索詳解流程
Lucene是一個(gè)基于Java的全文信息檢索工具包,它不是一個(gè)完整的搜索應(yīng)用程序,而是為你的應(yīng)用程序提供索引和搜索功能。Lucene 目前是 Apache Jakarta 家族中的一個(gè)開源項(xiàng)目,也是目前最為流行的基于 Java 開源全文檢索工具包2022-06-06Spring security如何實(shí)現(xiàn)記錄用戶登錄時(shí)間功能
這篇文章主要介紹了Spring security如何實(shí)現(xiàn)記錄用戶登錄時(shí)間功能,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03編寫調(diào)用新浪微博API的Java程序來發(fā)送微博
這篇文章主要介紹了編寫調(diào)用新浪微博API的Java程序來發(fā)送微博的方法,只是展示了一個(gè)基本的程序框架而非一個(gè)完整的圖形化軟件:)需要的朋友可以參考下2015-11-11java自定義日志輸出文件(log4j日志文件輸出多個(gè)自定義日志文件)
打印日志的在程序中是必不可少的,如果需要將不同的日志打印到不同的地方,則需要定義不同的Appender,然后定義每一個(gè)Appender的日志級(jí)別、打印形式和日志的輸出路徑,下面看一個(gè)示例吧2014-01-01jpa實(shí)現(xiàn)多對多的屬性時(shí)查詢的兩種方法
這篇文章主要介紹了jpa實(shí)現(xiàn)多對多的屬性時(shí)查詢的兩種方法,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11一個(gè)簡單的java學(xué)生寢室查詢系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了一個(gè)簡單的java學(xué)生寢室查詢系統(tǒng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10