前面在介紹使用AsyncRestTemplate來實現網絡異步請求時,當時提到在 Spring5+以後,建議經過 WebClient 來取代 AsyncRestTemplate 來實現異步網絡請求;html
那麼 WebClient 又是一個什麼東西呢,它是怎樣替代AsyncRestTemplate來實現異步請求的呢,接下來咱們將進入 Spring Web 工具篇中,比較重要的 WebClient 系列知識點,本文爲第一篇,基本使用姿式一覽react
I. 項目環境web
咱們依然採用 SpringBoot 來搭建項目,版本爲 2.2.1.RELEASE, maven3.2做爲構建工具,idea做爲開發環境spring
1. pom 依賴json
SpringBoot 相關的依賴就不貼出來了,有興趣的能夠查看源碼,下面是關鍵依賴後端
org.springframework.bootspring-boot-starter-webflux請注意一下上面的兩個依賴包,對於使用WebClient,主要須要引入spring-boot-starter-webflux包cookie
2. 測試 REST 接口網絡
接下來咱們直接在這個項目中寫幾個用於測試的 REST 接口,由於項目引入的 webflux 的依賴包,因此咱們這裏也採用 webflux 的註解方式,新增用於測試的 GET/POST 接口app
對於 WebFlux 用法不太清楚的小夥伴也沒有關係,WebClient 的發起的請求,後端是基於傳統的 Servlet 也是沒有問題的;關於 WebFlux 的知識點,將放在 WebClient 系列博文以後進行介紹異步
@DatapublicclassBody{ String name; Integer age;}@RestControllerpublicclassReactRest{@GetMapping(path = "header")public Mono header(@RequestHeader(name = "User-Agent") String userAgent,@RequestHeader(name = "ck", required = false) String cookie) {return Mono.just("userAgent is: [" + userAgent + "] ck: [" + cookie + "]");}@GetMapping(path = "get")public Mono get(String name, Integer age){return Mono.just("req: " + name + " age: " + age); }@GetMapping(path = "mget")public Flux mget(String name, Integer age){return Flux.fromArray(new String[]{"req name: " + name, "req age: " + age}); }/** * form表單傳參,映射到實體上 * * @param body * @return */@PostMapping(path = "post")public Mono post(Body body){return Mono.just("post req: " + body.getName() + " age: " + body.getAge()); }// 請注意,這種方式和上面的post方法二者不同,主要區別在Content-Type@PostMapping(path = "body")public Mono postBody(@RequestBody Body body){return Mono.just("body req: " + body); }}針對上面的兩個 POST 方法,雖然參數都是 Body,可是一個有@RequestBody,一個沒有,這裏須要額外注意
II. WebClient 使用說明
接下來咱們將進入 WebClient 的使用說明,主要針對最多見的 GET/POST 請求姿式進行實例展現,目標是看完下面的內容以後,能夠愉快的進行最基本(手動增強語氣)的 GET/POST 請求發送
如下全部內容,參考 or 啓發與官方文檔: https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-client
1. WebClient 建立姿式
通常有三種得到 WebClient 的方式,基於WebClient#create建立默認的 WebClient,基於WebClient#builder建立有自定義需求的 WebClient,基於已有的webclient#mutate建立
a. create 方式
這種屬於最多見,也是最基礎的建立方式,一般有兩種 case
WebClient.create()
WebClient.create(String baseUrl):與上面一個最主要的區別在於指定了 baseUrl,後面再發起的請求,就不須要重複這個 baseUrl 了;
舉例說明:baseUrl 指定爲http://127.0.0.1:8080;那麼後面的請求 url,直接填寫/get, /header, /post這種 path 路徑便可
下面給出一個實例說明
// 建立WebClient實例WebClient webClient= WebClient.create();// 發起get請求,並將返回的數據格式轉換爲String;由於是異步請求,因此返回的是Mono包裝的對象Mono ans = webClient.get().uri("http://127.0.0.1:8080/get?name=一灰灰&age=18").retrieve().bodyToMono(String .class);ans.subscribe(s -> System.out.println("create return: " + s));b. builder 方式
builder 方式最大的區別在於它能夠爲WebClient "賦能", 好比咱們但願全部的請求都有通用的請求頭、cookie 等,就能夠經過builder的方式,在建立WebClient的時候就進行指定
官方支持的可選配置以下:
uriBuilderFactory: Customized UriBuilderFactory to use as a base URL.
defaultHeader: Headers for every request.
defaultCookie: Cookies for every request.
defaultRequest: Consumer to customize every request.
filter: Client filter for every request.
exchangeStrategies: HTTP message reader/writer customizations.
clientConnector: HTTP client library settings.
關於上面這些東西有啥用,怎麼用,會在後續的系列博文中逐一進行介紹,這裏就不詳細展開;有興趣的小夥伴能夠關注收藏一波
給出一個設置默認 Header 的實例
webClient = WebClient.builder().defaultHeader("User-Agent", "WebClient Agent").build();ans = webClient.get().uri("http://127.0.0.1:8080/header").retrieve().bodyToMono(String.class);ans.subscribe(s -> System.out.println("builderCreate with header return: " + s));c. mutate 方式
這種方式主要是在一個已經存在的WebClient基礎上,再建立一個知足自定義需求的WebClient
爲何要這樣呢?
由於 WebClient 一旦建立,就是不可修改的
下面給出一個在 builder 建立基礎上,再添加 cookie 的實例
// 請注意WebClient建立完畢以後,不可修改,若是須要設置默認值,能夠藉助 mutate 繼承當前webclient的屬性,再進行擴展webClient = webClient.mutate().defaultCookie("ck", "--web--client--ck--").build();ans = webClient.get().uri("http://127.0.0.1:8080/header").retrieve().bodyToMono(String.class);ans.subscribe(s -> System.out.println("webClient#mutate with cookie return: " + s));d. 測試輸出
查看項目源碼的小夥伴,會看到上面三個代碼片斷是在同一個方法內部, 2. GET 請求
上面其實已經給出了 GET 的請求姿式,通常使用姿式也比較簡單,咱們須要重點關注一下這個傳參問題
常見的使用姿式
webClient.get().uri(xxx).retrieve().bodyToMono/bodyToFluxget 的傳參,除了在 uri 中直接寫死以外,還有幾種常見的寫法
a. uri 參數
可變參數
查看源碼的小夥伴,能夠看到 uri 方法的接口聲明爲一個可變參數,因此就有一種 uri 用佔位{}表示參數位置,後面的參數對應參數值的時候用方式
WebClient webClient = WebClient.create("http://127.0.0.1:8080");Mono ans = webClient.get().uri("/get?name={1}", "一灰灰").retrieve().bodyToMono(String.class);ans.subscribe(s -> System.out.println("basic get with one argument res: " + s));// p1對應後面第一個參數 "一灰灰" p2 對應後面第二個參數 18ans = webClient.get().uri("/get?name={p1}&age={p2}", "一灰灰", 18).retrieve().bodyToMono(String.class);ans.subscribe(s -> System.out.println("basic get with two arguments res: " + s));請注意,上面兩個參數的 case 中,p1 對應的是一灰灰,p2 對應的是18;這裏的 p1 和 p2 能夠替換爲任意的其餘字符,它們是按照順序進行填充的,即第一個參數值填在第一個{}坑位
map 參數映射
另一種方式就是經過 map 來綁定參數名與參數值之間的映射關係
// 使用map的方式,來映射參數Map uriVariables = new HashMap<>(4);uriVariables.put("p1", "一灰灰");uriVariables.put("p2", 19);Flux fAns =webClient.get().uri("/mget?name={p1}&age={p2}", uriVariables).retrieve().bodyToFlux(String.class);fAns.subscribe(s -> System.out.println("basic mget return: " + s));b. 獲取 ResponseEntity
請仔細觀察上面的使用姿式,調用了retrieve()方法,這個主要就是用來從返回結果中「摘出」responseBody,那麼若是咱們但願後去返回的請求頭,返回的狀態碼,則須要將這個方法替換爲exchange()
下面給出一個獲取返回的請求頭實例
// 獲取請求頭等相關信息Mono> response = webClient.get().uri("/get?name={p1}&age={p2}", "一灰灰", 18).exchange() .flatMap(r -> r.toEntity(String.class));response.subscribe( entity -> System.out.println("res headers: " + entity.getHeaders() + " body: " + entity.getBody()));和前面的時候姿式大同小異,至於 flatMap 這些知識點會放在後續的 WebFlux 中進行介紹,這裏知道它是用來 ResponseBody 格式轉換關鍵點便可
c. 測試返回
測試輸出結果以下(固然實際輸出順序和上面定義的前後也沒有什麼關係)
3. POST 請求
對於 post 請求,咱們通常最長關注的就是基本的表單傳參和 json body 方式傳遞,下面分別給與介紹
a. 表單參數
藉助MultiValueMap來保存表單參數用於提交
WebClient webClient = WebClient.create("http://127.0.0.1:8080");// 經過 MultiValueMap 方式投遞form表單MultiValueMap formData = new LinkedMultiValueMap<>(4);formData.add("name", "一灰灰Blog");formData.add("age", "18");// 請注意,官方文檔上提示,默認的ContentType就是"application/x-www-form-urlencoded",因此下面這個contentType是能夠不顯示設置的Mono ans = webClient.post().uri("/post")// .contentType(MediaType.APPLICATION_FORM_URLENCODED).bodyValue(formData).retrieve().bodyToMono(String.class);ans.subscribe(s -> System.out.println("post formData ans: " + s));上面註釋了一行contentType(MediaType.APPLICATION_FORM_URLENCODED),由於默認的 ContentType 就是這個了,因此不須要額外指定(固然手動指定也沒有任何毛病)
除了上面這種使用姿式以外,在官方教程上,還有一種寫法,特別注意下面這種寫法的傳參是用的body,而上面是bodyValue,千萬別用錯,否則...
// 請注意這種方式與上面最大的區別是 body 而不是 bodyValueans = webClient.post().uri("/post").body(BodyInserters.fromFormData(formData)).retrieve().bodyToMono(String.class);ans.subscribe(s -> System.out.println("post2 formData ans: " + s));b. json body 傳參
post 一個 json 串,能夠說是比較常見的 case 了,在 WebClient 中,使用這種方式特別特別簡單,感受比前面那個還方便
指定 ContentType
傳入 Object 對象
// post bodyBody body = new Body();body.setName("一灰灰");body.setAge(18);ans = webClient.post().uri("/body").contentType(MediaType.APPLICATION_JSON).bodyValue(body).retrieve().bodyToMono(String.class);ans.subscribe(s -> System.out.println("post body res: " + s));c. 測試輸出
4. 小結
本文爲 WebClient 系列第一篇,介紹 WebClient 的基本使用姿式,固然看完以後,發起 GET/POST 請求仍是沒有什麼問題的;可是僅限於此嘛?
builder 建立方式中,那些可選的條件都是啥,有什麼用,什麼場景下會用呢?
鄭州治療不孕不育http://www.zzchxbyy.com/
鄭州專業輸卵管醫院http://byby.zhengzhoutongjiyiyuan.com/
鄭州不孕不育醫院http://www.zzbybyyy120.com/
請求超時時間可設置麼?
能夠同步阻塞方式獲取返回結果嘛?
代理怎麼加
event-stream返回方式的數據怎麼處理
如何上傳文件
Basic Auth 身份鑑權
異步線程池可指定麼,可替換爲自定義的麼
返回非 200 狀態碼時,表現如何,又該如何處理
....