Spring中网络请求客户端WebClient的使用详解

Spring中网络请求客户端WebClient的使用详解_java_脚本之家

Spring5的WebClient使用详解-腾讯云开发者社区-腾讯云

在 Spring 5 之前,如果我们想要调用其他系统提供的 HTTP 服务,通常可以使用 Spring 提供的 RestTemplate 来访问,不过由于 RestTemplate 是 Spring 3 中引入的同步阻塞式 HTTP 客户端,因此存在一定性能瓶颈。根据 Spring 官方文档介绍,在将来的版本中它可能会被弃用。

​ 作为替代,Spring 官方已在 Spring 5 中引入了 WebClient 作为非阻塞式 Reactive HTTP 客户端。下面通过样例演示如何使用 WebClient。

一、基本介绍

1.什么是 WebClient

从 Spring 5 开始,Spring 中全面引入了 Reactive 响应式编程。而 WebClient 则是 Spring WebFlux 模块提供的一个非阻塞的基于响应式编程的进行 Http 请求的客户端工具。

由于 WebClient 的请求模式属于异步非阻塞,能够以少量固定的线程处理高并发的 HTTP 请求。因此,从 Spring 5 开始,HTTP 服务之间的通信我们就可以考虑使用 WebClient 来取代之前的 RestTemplate。

2.WebClient 的优势

(1)与 RestTemplate 相比,WebClient 有如下优势:

  • 非阻塞,Reactive 的,并支持更高的并发性和更少的硬件资源。
  • 提供利用 Java 8 lambdas 的函数 API。
  • 支持同步和异步方案。
  • 支持从服务器向上或向下流式传输。

(2)RestTemplate 不适合在非阻塞应用程序中使用,因此 Spring WebFlux 应用程序应始终使用 WebClient。在大多数高并发场景中,WebClient 也应该是 Spring MVC 中的首选,并且用于编写一系列远程,相互依赖的调用。

3.安装配置

编辑 pom.xml 文件,添加 Spring WebFlux 依赖,从而可以使用 WebClient。

1

2

3

4

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-webflux</artifactId>

</dependency>

二、创建 WebClient 实例

​ 从 WebClient 的源码中可以看出,WebClient 接口提供了三个不同的静态方法来创建 WebClient 实例:

1.利用 create() 创建

(1)下面利用 create() 方法创建一个 WebClient 对象,并利用该对象请求一个网络接口,最后将结果以字符串的形式打印出来。

注意:由于利用 create() 创建的 WebClient 对象没有设定 baseURL,所以这里的 uri() 方法相当于重写 baseURL。

1

2

3

4

5

6

7

8

9

WebClient webClient = WebClient.create();

  

Mono<String> mono = webClient

        .get() // GET 请求

        .uri("http://jsonplaceholder.typicode.com/posts/1")  // 请求路径

        .retrieve() // 获取响应体

        .bodyToMono(String.class); //响应数据类型转换

  

System.out.println(mono.block());

2.利用 create(String baseUrl) 创建

(1)下面利用 create(String baseUrl) 方法创建一个 WebClient 对象,并利用该对象请求一个网络接口,最后将结果以字符串的形式打印出来。

注意:由于利用 create(String baseUrl) 创建的 WebClient 对象时已经设定了 baseURL,所以 uri() 方法会将返回的结果和 baseUrl 进行拼接组成最终需要远程请求的资源 URL。

1

2

3

4

5

6

7

8

9

WebClient webClient = WebClient.create("http://jsonplaceholder.typicode.com");

  

Mono<String> mono = webClient

        .get() // GET 请求

        .uri("/posts/1"// 请求路径

        .retrieve() // 获取响应体

        .bodyToMono(String.class); //响应数据类型转换

  

System.out.println(mono.block());

3.利用 builder 创建(推荐)

(1)下面使用 builder() 返回一个 WebClient.Builder,然后再调用 build 就可以返回 WebClient 对象。并利用该对象请求一个网络接口,最后将结果以字符串的形式打印出来。

注意:由于返回的不是 WebClient 类型而是 WebClient.Builder,我们可以通过返回的 WebClient.Builder 设置一些配置参数(例如:baseUrl、header、cookie 等),然后再调用 build 就可以返回 WebClient 对象了

1

2

3

4

5

6

7

8

9

10

11

12

13

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() // 获取响应体

        .bodyToMono(String.class); //响应数据类型转换

          

System.out.println(mono.block());

三、GET 请求

1.获取 String 结果数据

下面代码将响应结果映射为一个 String 字符串,并打印出来。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

@RestController

public class HelloController {

  

    // 创建 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() // 获取响应体

                .bodyToMono(String.class); //响应数据类型转换

        System.out.println(mono.block());

        return;

    }

}

2.将结果转换为对象

(1)当响应的结果是 JSON 时,也可以直接指定为一个 Object,WebClient 将接收到响应后把 JSON 字符串转换为对应的对象。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

@RestController

public class HelloController {

  

    // 创建 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() // 获取响应体

                .bodyToMono(PostBean.class); //响应数据类型转换

        System.out.println(mono.block());

        return;

    }

}

(2)其中定义的实体 Bean 代码如下:

1

2

3

4

5

6

7

8

9

@Getter

@Setter

@ToString

public class PostBean {

    private int userId;

    private int id;

    private String title;

    private String body;

}

3.将结果转成集合

(1)假设接口返回的是一个 json 数组,内容如下:

(2)我们也可以将其转成对应的 Bean 集合:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

@RestController

public class HelloController {

  

    // 创建 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() // 获取响应体

                .bodyToFlux(PostBean.class); //响应数据类型转换

        List<PostBean> posts = flux.collectList().block();

        System.out.println("结果数:" + posts.size());

        return;

    }

}

4.参数传递的几种方式

下面 3 种方式的结果都是一样的。

(1)使用占位符的形式传递参数:

1

2

3

4

5

Mono<String> mono = webClient

        .get() // GET 请求

        .uri("/{1}/{2}", "posts", "1"// 请求路径

        .retrieve() // 获取响应体

        .bodyToMono(String.class); //响应数据类型转换

(2)另一种使用占位符的形式:

1

2

3

4

5

6

7

8

9

String type = "posts";

int id = 1;

  

Mono<String> mono = webClient

        .get() // GET 请求

        .uri("/{type}/{id}", type, id)  // 请求路径

        .retrieve() // 获取响应体

        .bodyToMono(String.class); //响应数据类型转换

        System.out.println(mono.block());

(3)我们也可以使用 map 装载参数:

1

2

3

4

5

6

7

8

9

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() // 获取响应体

        .bodyToMono(String.class); //响应数据类型转换

5.subscribe 订阅(非阻塞式调用)

(1)前面的样例我们都是人为地使用 block 方法来阻塞当前程序。其实 WebClient 是异步的,也就是说等待响应的同时不会阻塞正在执行的线程。只有在响应结果准备就绪时,才会发起通知。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

@RestController

public class HelloController {

  

    // 创建 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() // 获取响应体

                .bodyToMono(String.class); //响应数据类型转换

  

        // 订阅(异步处理结果)

        mono.subscribe(result -> {

            System.out.println(result);

        });

  

        System.out.println("--- end ---");

        return;

    }

}

附:使用 exchange() 方法获取完整的响应内容

1.方法介绍

(1)前面我们都是使用 retrieve() 方法直接获取到了响应的内容,如果我们想获取到响应的头信息、Cookie 等,可以在通过 WebClient 请求时把调用 retrieve() 改为调用 exchange()。

(2)通过 exchange() 方法可以访问到代表响应结果的对象,通过该对象我们可以获取响应码、contentType、contentLength、响应消息体等。

2.使用样例

下面代码请求一个网络接口,并将响应体、响应头、响应码打印出来。其中响应体的类型设置为 String。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

@RestController

public class HelloController {

  

    // 创建 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();

  

        // 获取完整的响应对象

        ClientResponse response = mono.block();

  

        HttpStatus statusCode = response.statusCode(); // 获取响应码

        int statusCodeValue = response.rawStatusCode(); // 获取响应码值

        Headers headers = response.headers(); // 获取响应头

  

        // 获取响应体

        Mono<String> resultMono = response.bodyToMono(String.class);

        String body = resultMono.block();

  

        // 输出结果

        System.out.println("statusCode:" + statusCode);

        System.out.println("statusCodeValue:" + statusCodeValue);

        System.out.println("headers:" + headers.asHttpHeaders());

        System.out.println("body:" + body);

        return;

    }

}

四、POST 请求

1.发送一个 JSON 格式数据(使用 json 字符串)

(1)下面代码使用 post 方式发送一个 json 格式的字符串,并将结果打印出来(以字符串的形式)。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

@RestController

public class HelloController {

  

    // 创建 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\": \"航歌\"}";

  

        // 发送请求

        Mono<String> mono = webClient

                .post() // POST 请求

                .uri("/posts"// 请求路径

                .contentType(MediaType.APPLICATION_JSON_UTF8)

                .body(BodyInserters.fromObject(jsonStr))

                .retrieve() // 获取响应体

                .bodyToMono(String.class); //响应数据类型转换

  

        // 输出结果

        System.out.println(mono.block());

        return;

    }

}

2.发送一个 JSON 格式数据(使用 Java Bean)

(1)下面代码使用 post 方式发送一个 Bean 对象,并将结果打印出来(以字符串的形式)。结果同上面是一样的:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

@RestController

public class HelloController {

  

    // 创建 WebClient 对象

    private WebClient webClient = WebClient.builder()

            .baseUrl("http://jsonplaceholder.typicode.com")

            .build();

  

    @GetMapping("/test")

    public void test() {

        // 要发送的数据对象

        PostBean postBean = new PostBean();

        postBean.setUserId(222);

        postBean.setTitle("abc");

        postBean.setBody("航歌");

  

        // 发送请求

        Mono<String> mono = webClient

                .post() // POST 请求

                .uri("/posts"// 请求路径

                .contentType(MediaType.APPLICATION_JSON_UTF8)

                .syncBody(postBean)

                .retrieve() // 获取响应体

                .bodyToMono(String.class); //响应数据类型转换

  

        // 输出结果

        System.out.println(mono.block());

        return;

    }

}

(2)上面发送的 Bean 对象实际上会转成如下格式的 JSON 数据提交:

3.使用 Form 表单的形式提交数据

(1)下面样例使用 POST 方式发送 multipart/form-data 格式的数据:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

@RestController

public class HelloController {

  

    // 创建 WebClient 对象

    private WebClient webClient = WebClient.builder()

            .baseUrl("http://jsonplaceholder.typicode.com")

            .build();

  

    @GetMapping("/test")

    public void test() {

        //提交参数设置

        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();

        map.add("title", "abc");

        map.add("body", "航歌");

  

        // 发送请求

        Mono<String> mono = webClient

                .post() // POST 请求

                .uri("/posts"// 请求路径

                .contentType(MediaType.APPLICATION_FORM_URLENCODED)

                .body(BodyInserters.fromFormData(map))

                .retrieve() // 获取响应体

                .bodyToMono(String.class); //响应数据类型转换

  

        // 输出结果

        System.out.println(mono.block());

        return;

    }

}

(2)上面代码最终会通过如下这种 form 表单方式提交数据:

4.将结果转成自定义对象

​ 上面样例我们都是将响应结果以 String 形式接收,其实 WebClient 还可以自动将响应结果转成自定的对象或则数组。具体可以参考前面写的文章:

5.设置 url 参数

(1)如果 url 地址上面需要传递一些参数,可以使用占位符的方式:

1

2

String url = "http://jsonplaceholder.typicode.com/{1}/{2}";

String url = "http://jsonplaceholder.typicode.com/{type}/{id}";

(2)具体的用法可以参考前面写的文章:

6.subscribe 订阅(非阻塞式调用)

(1)前面的样例我们都是人为地使用 block 方法来阻塞当前程序。其实 WebClient 是异步的,也就是说等待响应的同时不会阻塞正在执行的线程。只有在响应结果准备就绪时,才会发起通知。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

@RestController

public class HelloController {

  

    // 创建 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() // 获取响应体

                .bodyToMono(String.class); //响应数据类型转换

  

        // 订阅(异步处理结果)

        mono.subscribe(result -> {

            System.out.println(result);

        });

  

        System.out.println("--- end ---");

        return;

    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/29877.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

初识es(elasticsearch)

初识elasticsearch 什么是elasticsearch&#xff1f;&#xff1a; 一个开源的分部署搜索引擎、可以用来实现搜索、日志统计、分析、系统监控等功能。 什么是文档和词条&#xff1f; 每一条数据就是一个文档对文档中的内容进行分词&#xff0c;得到的词语就是词条 什么是正向…

【elementui源码解析】如何实现自动渲染md文档-第四篇

目录 1.前言 2.md-loader - index.js 1&#xff09;md.render() 2&#xff09;定义变量 3&#xff09;while stripTemplate stripScript genInlineComponentText 4&#xff09;pageScript 5&#xff09;return 6&#xff09;demo-block 3.总结 所有章节&#x…

HTML(9)——字体修饰

常用的字体修饰属性 属性描述font-size字体大小 单位&#xff1a;数字pxfont-weight字体粗细 单位&#xff1a;数字font-style 字体倾斜 line-height行高 单位&#xff1a;数字px/数字(当前size的倍数&#xff09;font-family字体族…

微纳米气泡发生器是微纳米气泡产生装置 未来市场需求将不断释放

微纳米气泡发生器是微纳米气泡产生装置 未来市场需求将不断释放 微纳米气泡发生器即微纳米气泡发生设备&#xff0c;是一种将水和气体混合并产生微纳米气泡的设备。微纳米气泡是指直径在100μm以下的气泡&#xff0c;分为纳米气泡和微米气泡。   微纳米气泡发生器主要由发生设…

录屏录音两不误!电脑录屏录音软件推荐(3款)

在数字化时代&#xff0c;电脑录屏录音软件已成为教学、演示、会议记录等领域不可或缺的工具。它们能够捕捉屏幕上的每一个动作&#xff0c;同时录制音频&#xff0c;为用户提供直观、生动的视听材料。本文将详细介绍三种常用的电脑录屏录音软件&#xff0c;帮助读者了解并掌握…

Flink parallelism 和 Slot 介绍

Flink Parallelism介绍 在Apache Flink中&#xff0c;Parallelism&#xff08;并行度&#xff09;是一个核心概念&#xff0c;它决定了Flink任务&#xff08;Task&#xff09;的并行执行程度。Parallelism指的是在Flink应用程序中&#xff0c;一个算子&#xff08;Operator&am…

误删的文件不在回收站如何找回?6个恢复秘诀分享!

“我刚刚误删了一些文件&#xff0c;但是在回收站中没有看到这部分文件&#xff0c;这种情况下还有方法可以找回误删的文件吗&#xff1f;在线等一个答案&#xff01;” 在数字化时代&#xff0c;文件的安全和完整性对于个人和企业都至关重要。然而&#xff0c;有时候由于疏忽或…

【Android】使用SeekBar控制数据的滚动

项目需求 有一个文本数据比较长&#xff0c;需要在文本右侧加一个SeekBar&#xff0c;然后根据SeekBar的上下滚动来控制文本的滚动。 项目实现 我们使用TextView来显示文本&#xff0c;但是文本比较长的话&#xff0c;需要在TextView外面套一个ScrollView&#xff0c;但是我…

【100个C++面试题和解答】

以下是100个C面试题&#xff0c;每个问题后面都附有简单的解答。请注意&#xff0c;这些解答可能不是最深入或最全面的&#xff0c;但它们应该能够帮助您了解问题的基本概念。 问题&#xff1a;C中的main函数必须返回什么类型&#xff1f; 解答&#xff1a;main函数必须返回int…

利用K8S技术栈打造个人私有云

1.三个节点&#xff1a;master&#xff0c;slave&#xff0c;client 在Kubernetes集群中&#xff0c;三个节点的职责分别如下&#xff1a; Master节点&#xff1a; docker&#xff1a;用于运行Docker容器。 etcd&#xff1a;一个分布式键值存储系统&#xff0c;用于保存Kuberne…

42、基于神经网络的训练堆叠自编码器进行图像分类(matlab)

1、训练堆叠自编码器进行图像分类的原理及流程 基于神经网络的训练堆叠自编码器进行图像分类的原理和流程如下&#xff1a; 堆叠自编码器&#xff08;Stacked Autoencoder&#xff09;是一种无监督学习算法&#xff0c;由多个自编码器&#xff08;Autoencoder&#xff09;堆叠…

面试专区|【32道HDFS高频题整理(附答案背诵版)】

简述什么是HDFS&#xff0c;以及HDFS作用 &#xff1f; HDFS&#xff0c;即Hadoop Distributed File System&#xff0c;是Hadoop分布式文件系统。它是一个专门为了存储大量数据而设计的文件系统&#xff0c;能够在廉价的商用硬件上提供高吞吐量的数据访问&#xff0c;非常适合…

python使用openai做个简单的大模型对话转发服务

python库 openaiflaskdataclassestyping 废话不多说直接上干货 # 引入相关库 import datetime import json import logging from logging.handlers import RotatingFileHandler from openai import OpenAI from flask import Flask, request, jsonify, Response from datacl…

宝塔软件默认安装位置

自带的JDK /usr/local/btjdk/jdk8Tomcat 各个版本都在bttomcat这个文件夹下面&#xff0c;用版本区分。tomcat_bak8是备份文件 /usr/local/bttomcat/tomcat8nginx /www/server/nginxnginx配置文件存放目录 /www/server/panel/vhost/nginxredis /www/server/redismysql /…

nextjs(持续学习中)

return ( <p className{${lusitana.className} text-xl text-gray-800 md:text-3xl md:leading-normal}> Welcome to Acme. This is the example for the{’ } Next.js Learn Course , brought to you by Vercel. ); } 在顶级 /public 文件夹下提供静态资产 **默认 /…

AI在创造还是毁掉音乐?

随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;它正在逐步渗透到各个领域&#xff0c;包括音乐创作。AI可以模拟人类作曲家&#xff0c;生成复杂的旋律和和声&#xff0c;甚至进行歌词创作。然而&#xff0c;这种技术进步引发了一个重要的争论&#xff1a;…

【数学】什么是方法矩估计?和最大似然估计是什么关系?

背景 方法矩估计&#xff08;Method of Moments Estimation&#xff09;和最大似然估计&#xff08;Maximum Likelihood Estimation, MLE&#xff09;是两种常用的参数估计方法。方法矩估计基于样本矩与总体矩的关系&#xff0c;通过样本数据计算样本矩来估计总体参数。最大似…

央国企财务专家的“专家课”——中国总会计师协会联合实在智能举办RPA专项培训

近日&#xff0c;中国总会计师协会正式举办了为期五天的「财务数字化思维与实用IT技能提升」专项培训&#xff0c;吸引了来自中铁十五局集团有限公司、中国航空工业规划设计院、中核核电运行管理有限公司、中国北方车辆有限公司、一汽物流有限公司等国企、事业单位及民营企业共…

【权威出版/投稿优惠】2024年水利水电与能源环境科学国际会议(WRHEES 2024)

2024 International Conference on Water Resources, Hydropower, Energy and Environmental Science 2024年水利水电与能源环境科学国际会议 【会议信息】 会议简称&#xff1a;WRHEES 2024 大会时间&#xff1a;点击查看 截稿时间&#xff1a;点击查看 大会地点&#xff1a;…

【Linux】进程间通信3——system V共享内存

1.system V进程间通信 管道通信本质是基于文件的&#xff0c;也就是说操作系统并没有为此做过多的设计工作&#xff0c;而system V IPC是操作系统特地设计的一种通信方式。但是不管怎么样&#xff0c;它们的本质都是一样的&#xff0c;都是在想尽办法让不同的进程看到同一份由操…