webclient是什么意思_【SpringBoot WEB 系列】WebClient 之基础使用姿势

d96830936b76102cdcea3c239f8eefc5.png

【SpringBoot WEB 系列】WebClient 之基础使用姿势

前面在介绍使用AsyncRestTemplate来实现网络异步请求时,当时提到在 Spring5+之后,建议通过 WebClient 来取代 AsyncRestTemplate 来实现异步网络请求;

那么 WebClient 又是一个什么东西呢,它是怎样替代AsyncRestTemplate来实现异步请求的呢,接下来我们将进入 Spring Web 工具篇中,比较重要的 WebClient 系列知识点,本文为第一篇,基本使用姿势一览

I. 项目环境

我们依然采用 SpringBoot 来搭建项目,版本为 2.2.1.RELEASE, maven3.2作为构建工具,idea作为开发环境

1. pom 依赖

SpringBoot 相关的依赖就不贴出来了,有兴趣的可以查看源码,下面是关键依赖

<dependencies>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webfluxartifactId>
    dependency>
dependencies>

请注意一下上面的两个依赖包,对于使用WebClient,主要需要引入spring-boot-starter-webflux

2. 测试 REST 接口

接下来我们直接在这个项目中写几个用于测试的 REST 接口,因为项目引入的 webflux 的依赖包,所以我们这里也采用 webflux 的注解方式,新增用于测试的 GET/POST 接口

对于 WebFlux 用法不太清楚的小伙伴也没有关系,WebClient 的发起的请求,后端是基于传统的 Servlet 也是没有问题的;关于 WebFlux 的知识点,将放在 WebClient 系列博文之后进行介绍

@Data
public class Body {
    String name;
    Integer age;
}

@RestController
public class ReactRest {

    @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,一个没有,这里需要额外注意

从下图也可以看出,两者的区别之处

3c90a998a8c5b8dcde9d657cd02b95eb.png

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. 测试输出

查看项目源码的小伙伴,会看到上面三个代码片段是在同一个方法内部,测试输出如下

2c960e02ef6ba6e4b4efd79a325d060b.png

你会看到一个有意思的地方,第一种基础的创建方式输出在第二种之后,这个是没有问题的哈(有疑问的小伙伴可以看一下文章开头,我们介绍 WebClient 的起因是啥)

2. GET 请求

上面其实已经给出了 GET 的请求姿势,一般使用姿势也比较简单,我们需要重点关注一下这个传参问题

常见的使用姿势

webClient.get().uri(xxx).retrieve().bodyToMono/bodyToFlux

get 的传参,除了在 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 对应后面第二个参数 18
ans = 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. 测试返回

测试输出结果如下(当然实际输出顺序和上面定义的先后也没有什么关系)

460677061736a8fdb2928054947e8cb7.png

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 而不是 bodyValue
ans = 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 body
Body 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. 测试输出
631cd08106511dc402a0591b2b82d574.png

4. 小结

本文为 WebClient 系列第一篇,介绍 WebClient 的基本使用姿势,当然看完之后,发起 GET/POST 请求还是没有什么问题的;但是仅限于此嘛?

  • builder 创建方式中,那些可选的条件都是啥,有什么用,什么场景下会用呢?
  • 请求超时时间可设置么?
  • 可以同步阻塞方式获取返回结果嘛?
  • 代理怎么加
  • event-stream返回方式的数据怎么处理
  • 如何上传文件
  • Basic Auth 身份鉴权
  • 异步线程池可指定么,可替换为自定义的么
  • 返回非 200 状态码时,表现如何,又该如何处理
  • ....

后续的系列博文将针对上面提出 or 尚未提出的问题,一一进行介绍,看到的各位大佬按按鼠标点赞收藏评论关注加个好友呗

II. 其他

0. 项目

  • 工程:https://github.com/liuyueyi/spring-boot-demo
  • 源码:https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/222-web-client

1. 一灰灰 Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

  • 一灰灰 Blog 个人博客 https://blog.hhui.top
  • 一灰灰 Blog-Spring 专题博客 http://spring.hhui.top
9ed1d885f97d8388845bdda99823259d.png
一灰灰blog

往期博文

  • 【SpringBoot WEB 系列】AsyncRestTemplate 之异步非阻塞网络请求介绍篇

  • 【SpringBoot WEB 系列】RestTemplate 之非 200 状态码信息捕获

  • 【SpringBoot WEB 系列】RestTemplate 之 Basic Auth 授权

  • 【SpringBoot WEB 系列】RestTemplate 之代理访问

  • 【SpringBoot WEB 系列】RestTemplate 之超时设置

  • 【SpringBoot WEB 系列】RestTemplate 之中文乱码问题 fix

  • 【SpringBoot WEB 系列】RestTemplate 之自定义请求头

  • 【SpringBoot WEB 系列】RestTemplate 基础用法小结

  • 【SpringBoot WEB系列】 RestTemplate 4xx/5xx 异常信息捕获

  • 【SpringBoot WEB系列】RestTempalte urlencode参数解析异常全程分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值