前面在介绍使用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 状态码时,表现如何,又该如何处理
....