WebClient

一、WebClient 概述

1.1 什么是 WebClient

  • WebClient 是 Spring 5 引入的一个 基于响应式编程模型 的 HTTP 客户端。
  • 与传统的 RestTemplate 相比,WebClient 采用了 Reactor 库,支持 非阻塞式(异步)调用,可充分利用多核 CPU 资源,提升高并发场景的吞吐量。
  • 它能够非常灵活地构造并发送 HTTP 请求(支持 GET、POST、PUT、DELETE、PATCH 等所有常见方法),并以 Mono/Flux)的方式处理响应结果。

1.2 适用场景

  • 高并发:在微服务或需要频繁调用远程服务的项目中,WebClient 的异步特性可以极大提升性能。
  • 响应式应用:如果你的项目基于 Spring WebFlux(响应式编程),WebClient 就是标配。
  • 替代 RestTemplate:Spring 官方逐渐建议用 WebClient 替代 RestTemplate,尤其在新项目中。

二、如何创建 WebClient

2.1 最简单的创建方式

WebClient webClient = WebClient.create();

解释

  • WebClient.create():调用 WebClient 的静态方法 create(),创建一个最简单的 WebClient 实例。
  • 没有 传入任何参数,意味着没有设置基础 URL,也没有任何默认请求头等配置,完全靠调用时手动指定。

2.2 带基础 URL 的创建方式

WebClient webClient = WebClient.create("https://api.example.com");

解释

  • 同样使用 WebClient.create(...),但这次传入一个字符串 https://api.example.com
  • 这表示 WebClient 在发起请求时,如果使用相对路径(如 /users),就会自动拼接这个基础路径(变成 https://api.example.com/users)。
  • 这样可以简化多次调用同一远程服务时的路径书写。

2.3 使用 Builder 进行更多配置

WebClient webClient = WebClient.builder().baseUrl("https://api.example.com").defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer my-token").build();

解释

  • WebClient.builder():返回一个 Builder 对象,允许配置更多细节。
  • .baseUrl("..."):设置基础 URL,作用与前一个示例类似。
  • .defaultHeader(HttpHeaders.CONTENT_TYPE, ...):为 所有 请求添加默认请求头,指定内容类型为 application/json
  • .defaultHeader(HttpHeaders.AUTHORIZATION, ...):设置授权信息(如 Bearer Token),方便需要身份认证的场景。
  • .build():最终构建出一个拥有上述配置的 WebClient 实例。

2.4 在 Spring 项目中注册为 Bean

@Configuration
public class WebClientConfig {@Beanpublic WebClient webClient(WebClient.Builder builder) {return builder.baseUrl("https://api.example.com").defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).build();}
}

解释

  • 这是一个 配置类@Configuration),Spring 会在容器启动时扫描并初始化它。
  • @Bean:声明一个 Bean 方法 webClient,返回类型是 WebClient
  • 方法参数 WebClient.Builder builder:Spring Boot 在启动时会自动注入一个 Builder。
  • .build():构建并返回一个带基础 URL 和默认请求头的 WebClient。
  • 这样,其他类中只需 @Autowired WebClient webClient; 就可直接使用。

三、WebClient 的核心方法与示例

3.1 通用流程

每次使用 WebClient 发起请求,大致分为三步:

  1. 指定 HTTP 方法(GET、POST、PUT、DELETE 等)和 URI
  2. 调用 .retrieve().exchangeToMono() 发送请求;
  3. 使用 .bodyToMono().bodyToFlux() 解析响应体。

3.2 GET 请求示例

Mono<User> userMono = webClient.get().uri("/users/{id}", 1).retrieve().bodyToMono(User.class);

解释

  • webClient.get():声明一个 GET 请求。
  • .uri("/users/{id}", 1):指定请求路径 /users/1,其中 {id} 会被 1 替换。
  • .retrieve():执行请求,并准备获取响应体;如果响应状态码是 4xx/5xx,将会自动抛出异常。
  • .bodyToMono(User.class):将响应 JSON 反序列化成 User 对象,并封装在一个 Mono<User> 中。
    • Mono 表示 “可能产生 0 或 1 个元素” 的流,在这里就意味着一个 User
  • WebClient 支持通过 ParameterizedTypeReference 指定泛型类型,从而避免原始类型的问题。new ParameterizedTypeReference<Map<String, Object>>() {} 会创建一个匿名类,保留泛型信息。
  • import org.springframework.core.ParameterizedTypeReference;
    import org.springframework.web.reactive.function.client.WebClient;Map<String, Object> response = webClient.get().uri(url).retrieve().bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {}).block();

如何拿到 User?

  • 阻塞方式(不推荐在高并发场景):
    User user = userMono.block();
    System.out.println(user.getName());
    
  • 异步方式(推荐):
    userMono.subscribe(user -> {System.out.println(user.getName());
    });
    System.out.println("我先执行,不会阻塞等待");
    


3.3 POST 请求示例

User newUser = new User(2, "Alice");Mono<User> createdUserMono = webClient.post().uri("/users").contentType(MediaType.APPLICATION_JSON) .bodyValue(newUser).retrieve().bodyToMono(User.class);

解释

  • webClient.post():声明一个 POST 请求。
  • .uri("/users"):目标路径为 /users
  • .contentType(...):设置请求体的 Content-Type 为 application/json
  • .bodyValue(newUser):将 newUser 对象作为请求体发送。
  • .retrieve():执行请求并返回响应。
  • .bodyToMono(User.class):将响应体 JSON 解析为一个 User 对象,封装在 Mono 中。

3.4 PUT 请求示例

User updatedUser = new User(1, "Updated Name");webClient.put().uri("/users/{id}", 1).contentType(MediaType.APPLICATION_JSON).bodyValue(updatedUser).retrieve().bodyToMono(Void.class).block();

解释

  • webClient.put():声明一个 PUT 请求。
  • .uri("/users/{id}", 1):目标资源是 /users/1
  • .bodyValue(updatedUser):更新的用户数据。
  • .retrieve().bodyToMono(Void.class):PUT 请求通常不返回数据,这里用 Void.class 代表空。
  • .block():此处为了示例,使用阻塞方式直接等待请求完成。

3.5 DELETE 请求示例

webClient.delete().uri("/users/{id}", 1).retrieve().bodyToMono(Void.class).block();

解释

  • webClient.delete():声明一个 DELETE 请求。
  • .uri("/users/{id}", 1):删除路径 /users/1
  • 同样 .retrieve() -> .bodyToMono(Void.class),一般 DELETE 请求也不会返回体。
  • 最后 .block() 以便我们知道删除完成(在真实响应式场景也可用 .subscribe())。

四、响应式编程中的常见操作

4.1 MonoFlux 区别

  • Mono<T>:最多产生一个元素(0 或 1 个)。
  • Flux<T>:可以产生多个元素,类似 “数据流”。

如何把 Flux<User> 转为列表?

Flux<User> userFlux = webClient.get().uri("/users").retrieve().bodyToFlux(User.class);List<User> userList = userFlux.collectList().block();
  • .collectList():将流中的所有 User 收集为一个 List<User>
  • .block():阻塞方式拿到列表。

4.2 block()subscribe()

  • block():阻塞当前线程,直到 MonoFlux 产生结果。

    • 简化代码,但失去异步优势。
    • 在响应式编程中尽量少用,除非在测试或必须同步的场合。
  • subscribe():非阻塞,注册回调函数。

    • 当数据就绪时,会自动调用回调。
    • 适合真正的响应式应用场景。

五、高级特性与配置

5.1 自定义错误处理

默认情况下,.retrieve() 在遇到 4xx/5xx 状态码时会抛出 WebClientResponseException。可以自定义处理:

Mono<User> userMono = webClient.get().uri("/users/{id}", 999) // 假设该用户不存在.retrieve().onStatus(HttpStatus::is4xxClientError, response -> response.bodyToMono(String.class).flatMap(err -> Mono.error(new RuntimeException("Client Error: " + err)))).onStatus(HttpStatus::is5xxServerError, response -> response.bodyToMono(String.class).flatMap(err -> Mono.error(new RuntimeException("Server Error: " + err)))).bodyToMono(User.class);userMono.subscribe(user -> System.out.println("User: " + user),error -> System.err.println("Error: " + error.getMessage())
);

解释

  • .onStatus(...):自定义对特定状态码的处理。
  • bodyToMono(String.class):这里获取错误信息(如后端返回的错误描述)。
  • Mono.error(new RuntimeException(...)):抛出自定义异常,交给后续处理。
  • subscribe(...):处理正常结果和错误结果。

5.2 并发请求与结果合并

当需要同时调用多个接口时,可以并行发起多个请求,然后合并结果。例如,一个请求获取 User,另一个请求获取 Order

Mono<User> userMono = webClient.get().uri("/users/{id}", 1).retrieve().bodyToMono(User.class);Mono<Order> orderMono = webClient.get().uri("/orders/{id}", 101).retrieve().bodyToMono(Order.class);Mono<String> combined = Mono.zip(userMono, orderMono).map(tuple -> {User user = tuple.getT1();Order order = tuple.getT2();return "User: " + user.getName() + ", Order: " + order.getProductName();});combined.subscribe(System.out::println);

解释

  • Mono<User> userMono:获取用户数据的 Mono。
  • Mono<Order> orderMono:获取订单数据的 Mono。
  • Mono.zip(userMono, orderMono):并行执行两个 Mono,并在都完成后合并结果。
  • tuple.getT1()/getT2():分别代表第一个和第二个 Mono 的结果。
  • .subscribe(...):非阻塞执行,最后打印结果。

5.3 超时设置

可以通过整合 Reactor Netty 配置超时。示例如下:

import reactor.netty.http.client.HttpClient;WebClient webClient = WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create().responseTimeout(Duration.ofSeconds(5)) // 响应超时)).build();

解释

  • HttpClient.create():创建一个 Reactor Netty 的 HTTP 客户端。
  • .responseTimeout(Duration.ofSeconds(5)):表示如果在 5 秒内没收到响应,就会超时。
  • new ReactorClientHttpConnector(...):将自定义的 HttpClient 注入到 WebClient 中。
  • .build():构建出带超时配置的 WebClient。

5.4 文件上传

假设后端接口接收一个名为 file 的字段(Multipart),可通过以下代码:

MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
formData.add("file", new FileSystemResource("path/to/file.jpg"));webClient.post().uri("/upload").body(BodyInserters.fromMultipartData(formData)).retrieve().bodyToMono(String.class).subscribe(response -> System.out.println("Upload response: " + response));

解释

  • MultiValueMap<String, Object> formData:多值映射,用于构建 multipart/form-data 请求体。
  • new FileSystemResource("path/to/file.jpg"):将本地文件包装成一个 Resource 对象。
  • .body(BodyInserters.fromMultipartData(formData)):把上面的表单数据作为请求体(Multipart)。
  • .retrieve().bodyToMono(String.class):接收服务器返回的字符串(如上传结果)。
  • .subscribe(...):异步处理结果。

六、.retrieve().exchangeToMono() 区别

  • .retrieve()

    • 简化操作,自动 在 4xx/5xx 异常时抛出 WebClientResponseException
    • 无法直接访问响应头或状态码,需通过 .onStatus() 做额外处理。
    • 推荐在只关心响应体时使用。
  • .exchangeToMono()

    • 返回 ClientResponse,可访问 全部 响应信息(状态码、头部、Body)。
    • 需要手动判断状态码并决定下一步操作。
    • 适用于需要更灵活控制响应的场景。

示例

Mono<User> userMono = webClient.get().uri("/users/{id}", 1).exchangeToMono(response -> {if (response.statusCode().is2xxSuccessful()) {// 成功则将 body 转为 Userreturn response.bodyToMono(User.class);} else {// 非 2xx,拿到错误体或抛出异常return response.createException().flatMap(Mono::error);}});

七、总结

  • WebClient 优势

    • 非阻塞:在高并发场景下能更高效地使用线程。
    • 响应式:与 MonoFlux 完整配合,支持流式数据和异步操作。
    • 强大灵活:可自定义请求头、请求体、超时、拦截器,还可应对文件上传、并发调用等复杂场景。
    • Spring 官方推荐:新项目或使用 Spring WebFlux 的场景下,优先使用 WebClient 而非 RestTemplate。
  • 注意事项

    • 异步场景下尽量使用 .subscribe(...),避免滥用 .block() 导致阻塞。
    • 如果需要更复杂的响应处理(访问响应头或状态码),考虑用 .exchangeToMono() 代替 .retrieve()
    • 在生产环境,合理设置超时、重试、错误处理等,以避免调用远程服务不可靠导致的问题。

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

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

相关文章

Unity【Colliders碰撞器】和【Rigibody刚体】的应用——小球反弹效果

目录 Collider 2D 定义&#xff1a; 类型&#xff1a; Rigidbody 2D 定义&#xff1a; 属性和行为&#xff1a; 运动控制&#xff1a; 碰撞检测&#xff1a; 结合使用 实用检测 延伸拓展 1、在Unity中优化Collider 2D和Rigidbody 2D的性能 2、Unity中Collider 2D…

[微服务]redis主从集群搭建与优化

搭建主从集群 单节点Redis的并发能力是有上限的&#xff0c;要进一步提高Redis的并发能力&#xff0c;就需要搭建主从集群&#xff0c;实现读写分离。 1. 主从集群结构 下图就是一个简单的Redis主从集群结构&#xff1a; 如图所示&#xff0c;集群中有一个master节点、两个s…

自动化立体仓库堆垛机SRM控制系统自动控制功能块开发

1、堆垛机SRM控制系统硬件组态如下图 2、堆垛机SRM控制系统HMI屏幕页面如下图 驱动状态显示、堆垛机状态 3、堆垛机SRM控制系统中相关变量定义如下图 其中包含系统控制相关变量:系统急停、系统手动、复位、货叉左极限、货叉左居中 货叉右极限 货叉右居中 货叉编码器位置反…

【数据结构】栈与队列(FIFO)

在阅读该篇文章之前&#xff0c;可以先了解一下堆栈寄存器和栈帧的运作原理&#xff1a;<【操作系统】堆栈寄存器sp详解以及栈帧>。 栈(FILO) 特性: 栈区的存储遵循着先进后出的原则。 例子: 枪的弹夹&#xff0c;最先装进去的子弹最后射出来&#xff0c;最后装入的子弹…

黑马JavaWeb开发跟学(十五).Maven高级

黑马JavaWeb开发跟学.十五.Maven高级 Maven高级1. 分模块设计与开发1.1 介绍1.2 实践1.2.1 分析1.2.2 实现 1.3 总结 2. 继承与聚合2.1 继承2.1.1 继承关系2.1.1.1 思路分析2.1.1.2 实现 2.1.2 版本锁定2.1.2.1 场景2.1.2.2 介绍2.1.2.3 实现2.1.2.4 属性配置 2.2 聚合2.2.1 介…

入门级容器技术解析:Docker和K8s的区别与关系

目录 &#x1f3af;学习小目标&#xff1a; 关于容器 传统物理机&#x1f5a5;️ 虚拟机&#x1f4bb; 为什么使用容器技术呢&#xff1f;&#x1f914; 容器技术&#x1f943; Docker—容器化平台 K8s(Kubernetes)—容器编排系统​ Docker和K8s有什么关系和区别&#…

凌鸥电机开发学习记录

文章目录 9、凌鸥库函数软件过流点设定值BUG8、系统初始化7、ADC触发周期设定6、电机参数测量5、有感HALL相序问题4、电机参数问题3、PWM频率设置2、IO口对应问题1、供电问题 9、凌鸥库函数软件过流点设定值BUG 在软件过流点的判断中&#xff0c;是以当前三相电流和经过了内部…

怎样修改el-table主题样式

起因&#xff1a;el-table有主题样式&#xff0c;部分需要单独设置 环境&#xff1a;ideanodejs插件谷歌浏览器 第一步&#xff1a;找到scss文件&#xff1a; 谷歌浏览器打开表格页面&#xff0c;ctrlshifti打开开发者工具&#xff0c;点击后鼠标移动到表格单元格上单击一下…

Flink operator实现自动扩缩容

官网文档位置&#xff1a; 1.Autoscaler | Apache Flink Kubernetes Operator 2.Configuration | Apache Flink Kubernetes Operator 1.部署K8S集群 可参照我之前的文章k8s集群搭建 2.Helm安装Flink-Operator helm repo add flink-operator-repo https://downloads.apach…

从入门到精通:Ansible Shell 模块的应用与最佳实践

Ansible是一款强大的自动化运维工具&#xff0c;通过其模块化的设计&#xff0c;可以方便地管理和配置远程主机。作为Ansible的一个常用模块&#xff0c;shell 模块使得我们可以在目标主机上执行复杂的命令或脚本。无论是单一的命令&#xff0c;还是复杂的Shell脚本&#xff0c…

Linux应用软件编程--网络通信(传输层:udp协议,tcp协议,应用层:http协议)

网络通信&#xff1a;不同主机&#xff0c;进程间通信&#xff0c;分为广域网和局域网 OSI 七层模型&#xff1a;是一种理论模型 应用层&#xff1a;通信传输的数据内容 http、FTP、TFTP、MQTT 表述层&#xff1a;数据加密&#xff0c;解密操作&#xff0c;压缩&#xff…

鸿蒙的APP真机调试以及发布

目录&#xff1a; 1、创建好鸿蒙项目2、创建AGC项目3、实现自动签名3.1、手动方式创建签名文件和密码 4、运行项目5、无线真机调试 1、创建好鸿蒙项目 2、创建AGC项目 &#xff08;1&#xff09;在File->Project Structure->Project->Signing Configs中进行登录。(未…

n8n - AI自动化工作流

文章目录 一、关于 n8n关键能力n8n 是什么意思 二、快速上手 一、关于 n8n n8n是一个具有原生AI功能的工作流自动化平台&#xff0c;它为技术团队提供了代码的灵活性和无代码的速度。凭借400多种集成、原生人工智能功能和公平代码许可证&#xff0c;n8n可让您构建强大的自动化…

【Shell脚本】Docker构建Java项目,并自动停止原镜像容器,发布新版本

本文简述 经常使用docker部署SpringBoot 项目&#xff0c;因为自己的服务器小且项目简单&#xff0c;因此没有使用自动化部署。每次将jar包传到服务器后&#xff0c;需要手动构建&#xff0c;然后停止原有容器&#xff0c;并使用新的镜像启动&#xff0c;介于AI时代越来越懒的…

jmeter 中 BeanShell 预处理程序、JSR223后置处理程序使用示例

1. 各个组件如何新建的&#xff1f; 2. "http请求" 组件内容样例&#xff1a; "消息体数据" 源码&#xff1a; {"task_tag": "face_detect","image_type": "base64","extra_args": [{"model"…

K8s高可用集群之Kubernetes集群管理平台、命令补全工具、资源监控工具部署及常用命令

K8s高可用集群之Kubernetes管理平台、补全命令工具、资源监控工具部署及常用命令 1.Kuboard可视化管理平台2.kubectl命令tab补全工具3.MetricsServer资源监控工具4.Kubernetes常用命令 1.Kuboard可视化管理平台 可以选择安装k8s官网的管理平台&#xff1b;我这里是安装的其他开…

Centos源码安装MariaDB 基于GTID主从部署(一遍过)

MariaDB安装 安装依赖 yum install cmake ncurses ncurses-devel bison 下载源码 // 下载源码 wget https://downloads.mariadb.org/interstitial/mariadb-10.6.20/source/mariadb-10.6.20.tar.gz // 解压源码 tar xzvf mariadb-10.5.9.tar.gz 编译安装 cmake -DCMAKE_INSTA…

github gitbook写书

github创建新的仓库 在仓库中添加目录 ‘SUMMARY.md # Summary * [简介](README.md)gitbook 新建一个site https://www.gitbook.com/ 注册账号 取名字 一路 next&#xff0c;注意选免费版 最后 gitbook同步到github 你在主页可以看到 刚刚的test网站 点击右上角圈出来…

colnames看似简单,却能优化数据处理流程

引言 在数据处理和分析中&#xff0c;变量名称是至关重要的&#xff0c;它们决定了数据的可读性和操作的简便性。在R语言中&#xff0c;colnames 函数以其简单的语法设计&#xff0c;提供了高效管理数据框列名的能力&#xff0c;尤其是在复杂的爬虫任务中显得尤为重要。本篇文…

2025新春烟花代码(一)HTML5夜景放烟花绽放动画效果

标题预览效果 标题HTML代码 <!DOCTYPE html> <html lang"en"> <script>var _hmt _hmt || [];(function () {var hm document.createElement("script");hm.src "https://hm.baidu.com/hm.js?45f95f1bfde85c7777c3d1157e8c2d34&…