Spring Boot 的 WebClient 实践教程

什么是 WebClient?

在 Spring Boot 中,WebClient 是 Spring WebFlux 提供的一个非阻塞、响应式的 HTTP 客户端,用于与 RESTful 服务或其他 HTTP 服务交互。相比于传统的 RestTemplate,WebClient 更加现代化,具有异步和非阻塞的特点,适合高性能、高并发的应用场景。

WebClient 的特点

非阻塞 I/O:适用于响应式编程模型,能高效处理大量并发请求。

功能强大:支持同步和异步调用,处理复杂的 HTTP 请求和响应,包括流式数据。

灵活的配置:可自定义超时、请求拦截器、认证方式等。

响应式编程支持:返回 Mono 或 Flux,与 Spring WebFlux 的响应式编程模型无缝集成。

引入依赖

在使用 WebClient 之前,需要确保 Spring Boot 项目已包含相关依赖。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

配置及使用 WebClient

现在有以下服务

  • service1服务:http://localhost:8081/
  • service2服务:http://localhost:8082/
  • common服务:http://localhost:8079/

创建 WebClientConfig 配置类,为 service1 和 service2 配置独立的 WebClient。

package com.example.common.config;import io.netty.channel.ChannelOption;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.netty.tcp.TcpClient;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;/*** 配置 WebClient,支持基础功能(独立 WebClient 实例)和高级特性(超时、拦截器、内存限制)。*/
@Configuration
public class WebClientConfig {/*** 配置 WebClient,用于调用 service1(http://localhost:8081)** @param builder WebClient.Builder 实例* @return 针对 service1 的 WebClient 实例*/@Bean(name = "service1WebClient")public WebClient service1WebClient(WebClient.Builder builder) {return builder.baseUrl("http://localhost:8081") // 配置 service1 的基本 URL.defaultHeader("Content-Type", "application/json") // 设置默认请求头.exchangeStrategies(ExchangeStrategies.builder().codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024)) // 设置最大内存限制为 16MB.build()).filter(logRequest()) // 添加请求日志拦截器.filter(logResponse()) // 添加响应日志拦截器.build();}/*** 配置 WebClient,用于调用 service2(http://localhost:8082)** @param builder WebClient.Builder 实例* @return 针对 service2 的 WebClient 实例*/@Bean(name = "service2WebClient")public WebClient service2WebClient(WebClient.Builder builder) {return builder.baseUrl("http://localhost:8082") // 配置 service2 的基本 URL.defaultHeader("Content-Type", "application/json") // 设置默认请求头.filter(logRequest()) // 添加请求日志拦截器.filter(logResponse()) // 添加响应日志拦截器.build();}/*** 提供全局的 WebClient.Builder 配置,支持超时和高级功能。** @return 配置好的 WebClient.Builder*/@Beanpublic WebClient.Builder webClientBuilder() {// 配置 TCP 客户端,设置连接超时、读超时和写超时TcpClient tcpClient = TcpClient.create().option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) // 连接超时 5秒.doOnConnected(connection ->connection.addHandlerLast(new ReadTimeoutHandler(5)) // 读超时 5秒.addHandlerLast(new WriteTimeoutHandler(5))); // 写超时 5秒// 使用配置的 TcpClient 创建 HttpClientHttpClient httpClient = HttpClient.from(tcpClient);// 创建 WebClient.Builder 并配置 HttpClient 和拦截器return WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient)) // 配置 HttpClient.filter(logRequest()) // 请求日志拦截器.filter(logResponse()); // 响应日志拦截器}/*** 请求日志拦截器:记录请求的详细信息(方法和 URL)** @return ExchangeFilterFunction 拦截器*/private ExchangeFilterFunction logRequest() {return ExchangeFilterFunction.ofRequestProcessor(request -> {System.out.println("Request: " + request.method() + " " + request.url());return Mono.just(request);});}/*** 响应日志拦截器:记录响应的状态码** @return ExchangeFilterFunction 拦截器*/private ExchangeFilterFunction logResponse() {return ExchangeFilterFunction.ofResponseProcessor(response -> {System.out.println("Response status: " + response.statusCode());return Mono.just(response);});}
}

service1相应的接口

package cloud.service1.controller;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.Map;/*** Service1 的控制器类,用于处理与API相关的请求.* 该类被Spring框架管理,作为处理HTTP请求的一部分.*/
@RestController
@RequestMapping("/api/service1")
public class Service1Controller {/*** 获取Service1的数据信息.* * @return 包含服务信息的映射,包括服务名称和问候消息.*/@GetMapping("/data")public Map<String, String> getData() {// 返回一个不可变的映射,包含服务名称和问候消息return Map.of("service", "service1", "message", "Hello from Service1");}
}

service2相应的接口

package cloud.service2.controller;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.Map;/*** Service2的控制器类,用于处理与Service2相关的HTTP请求.* 该类被Spring框架管理,作为处理RESTful请求的控制器.*/
@RestController
@RequestMapping("/api/service2")
public class Service2Controller {/*** 处理GET请求到/api/service2/info,返回Service2的信息.* * @return 包含服务信息的Map,包括服务名称和欢迎消息.*/@GetMapping("/info")public Map<String, String> getInfo() {return Map.of("service", "service2", "message", "Hello from Service2");}
}

服务调用实现

package com.example.common.service;import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;/*** CommonService 类提供了对其他服务进行调用的方法* 它通过 WebClient 实例与 service1 和 service2 进行通信*/
@Service
public class CommonService {// 用于与 service1 通信的 WebClient 实例private final WebClient service1WebClient;// 用于与 service2 通信的 WebClient 实例private final WebClient service2WebClient;/*** 构造函数注入两个 WebClient 实例** @param service1WebClient 用于 service1 的 WebClient* @param service2WebClient 用于 service2 的 WebClient*/public CommonService(@Qualifier("service1WebClient") WebClient service1WebClient,@Qualifier("service2WebClient") WebClient service2WebClient) {this.service1WebClient = service1WebClient;this.service2WebClient = service2WebClient;}/*** 调用 service1 的接口** @return 来自 service1 的数据*/public Mono<String> callService1() {// 通过 service1WebClient 调用 service1 的 API,并处理可能的错误return service1WebClient.get().uri("/api/service1/data").retrieve().bodyToMono(String.class).onErrorResume(e -> {// 错误处理:打印错误信息并返回错误提示System.err.println("Error calling service1: " + e.getMessage());return Mono.just("Error calling service1");});}/*** 调用 service2 的接口** @return 来自 service2 的数据*/public Mono<String> callService2() {// 通过 service2WebClient 调用 service2 的 API,并处理可能的错误return service2WebClient.get().uri("/api/service2/info").retrieve().bodyToMono(String.class).onErrorResume(e -> {// 错误处理:打印错误信息并返回错误提示System.err.println("Error calling service2: " + e.getMessage());return Mono.just("Error calling service2");});}
}
package com.example.common.controller;import com.example.common.service.CommonService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;/*** 通用控制器类,处理与通用服务相关的API请求*/
@RestController
@RequestMapping("/api/common")
public class CommonController {// 注入通用服务接口,用于调用具体的服务方法private final CommonService commonService;/*** 构造函数注入CommonService实例** @param commonService 通用服务接口实例*/public CommonController(CommonService commonService) {this.commonService = commonService;}/*** 调用 service1 的接口** @return service1 的响应数据*/@GetMapping("/service1")public Mono<String> getService1Data() {return commonService.callService1();}/*** 调用 service2 的接口** @return service2 的响应数据*/@GetMapping("/service2")public Mono<String> getService2Info() {return commonService.callService2();}
}

测试接口

优化实践

将上述代码进一步优化和整合以确保代码可维护性和高效性。

package com.example.common.config;import io.netty.channel.ChannelOption;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.netty.tcp.TcpClient;/*** 配置 WebClient 的各类设置和日志记录*/
@Configuration
public class WebClientConfig {/*** 全局 WebClient.Builder 配置** @return 配置好的 WebClient.Builder*/@Beanpublic WebClient.Builder webClientBuilder() {// 配置 TCP 客户端的连接、读取、写入超时时间TcpClient tcpClient = TcpClient.create().option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) // 连接超时.doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(5)) // 读超时.addHandlerLast(new WriteTimeoutHandler(5))); // 写超时// 将 TCP 客户端配置应用到 HTTP 客户端HttpClient httpClient = HttpClient.from(tcpClient);// 配置 WebClient 构建器,包括 HTTP 连接器、交换策略、请求和响应日志return WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient)).exchangeStrategies(ExchangeStrategies.builder().codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024)) // 内存限制.build()).filter(logRequest())  // 请求日志.filter(logResponse()); // 响应日志}/*** 针对 service1 的 WebClient 配置** @param builder 全局配置的 WebClient.Builder* @return 配置好的 WebClient 实例*/@Bean(name = "service1WebClient")public WebClient service1WebClient(WebClient.Builder builder) {// 为 service1 配置特定的 base URL 和默认头部return builder.baseUrl("http://localhost:8081").defaultHeader("Content-Type", "application/json").build();}/*** 针对 service2 的 WebClient 配置** @param builder 全局配置的 WebClient.Builder* @return 配置好的 WebClient 实例*/@Bean(name = "service2WebClient")public WebClient service2WebClient(WebClient.Builder builder) {// 为 service2 配置特定的 base URL 和默认头部return builder.baseUrl("http://localhost:8082").defaultHeader("Content-Type", "application/json").build();}/*** 请求日志拦截器** @return 记录请求日志的 ExchangeFilterFunction*/private ExchangeFilterFunction logRequest() {// 拦截请求并打印请求方法和URLreturn ExchangeFilterFunction.ofRequestProcessor(request -> {System.out.println("Request: " + request.method() + " " + request.url());return Mono.just(request);});}/*** 响应日志拦截器** @return 记录响应日志的 ExchangeFilterFunction*/private ExchangeFilterFunction logResponse() {// 拦截响应并打印响应状态码return ExchangeFilterFunction.ofResponseProcessor(response -> {System.out.println("Response status: " + response.statusCode());return Mono.just(response);});}
}
package com.example.common.service;import org.springframework.core.ParameterizedTypeReference;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;import java.util.Map;/*** CommonService 类提供了调用两个不同服务的公共方法,并合并其结果*/
@Service
public class CommonService {// service1 的 WebClient 实例private final WebClient service1WebClient;// service2 的 WebClient 实例private final WebClient service2WebClient;/*** 构造函数注入 WebClient 实例** @param service1WebClient service1 的 WebClient* @param service2WebClient service2 的 WebClient*/public CommonService(WebClient service1WebClient, WebClient service2WebClient) {this.service1WebClient = service1WebClient;this.service2WebClient = service2WebClient;}/*** 异步调用 service1 和 service2,并返回合并结果(JSON 格式)** @return 包含两个服务响应的 Mono 对象*/public Mono<Map<String, Map<String, String>>> callServicesAsync() {// 调用 service1,返回 Map 响应Mono<Map<String, String>> service1Response = service1WebClient.get()// 设置请求的URI.uri("/api/service1/data")// 检索响应.retrieve()// 处理错误状态.onStatus(// 检查状态是否为4xx或5xxstatus -> status.is4xxClientError() || status.is5xxServerError(),// 如果是,创建一个运行时异常response -> Mono.error(new RuntimeException("Service1 Error: " + response.statusCode())))// 将响应体转换为Mono<Map<String, String>>.bodyToMono(new ParameterizedTypeReference<Map<String, String>>() {})// 处理错误.onErrorResume(e -> {// 打印错误信息System.err.println("Error calling service1: " + e.getMessage());// 返回一个包含错误信息的Mapreturn Mono.just(Map.of("error", "Fallback response for service1"));});// 调用 service2,返回 Map 响应Mono<Map<String, String>> service2Response = service2WebClient.get()// 设置请求的URI.uri("/api/service2/info")// 检索响应.retrieve()// 处理错误状态.onStatus(// 检查状态是否为4xx或5xxstatus -> status.is4xxClientError() || status.is5xxServerError(),// 如果是,创建一个运行时异常response -> Mono.error(new RuntimeException("Service2 Error: " + response.statusCode())))// 将响应体转换为Mono<Map<String, String>>.bodyToMono(new ParameterizedTypeReference<Map<String, String>>() {})// 处理错误.onErrorResume(e -> {// 打印错误信息System.err.println("Error calling service2: " + e.getMessage());// 返回一个包含错误信息的Mapreturn Mono.just(Map.of("error", "Fallback response for service2"));});// 合并两个响应return Mono.zip(service1Response, service2Response, (response1, response2) -> Map.of("service1", response1,"service2", response2))// 处理合并过程中的错误.onErrorResume(e -> {// 打印错误信息System.err.println("Error combining responses: " + e.getMessage());// 返回一个包含错误信息的Mapreturn Mono.just(Map.of("error", Map.of("status", "error","message", e.getMessage() // 捕获异常并输出信息)));});}
}
package com.example.common.controller;import com.example.common.service.CommonService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;import java.util.Map;@RestController
@RequestMapping("/api/common")
public class CommonController {private final CommonService commonService;public CommonController(CommonService commonService) {this.commonService = commonService;}/*** 提供异步调用的 REST 接口,返回 JSON 格式的数据*/@GetMapping("/service")public Mono<Map<String, Map<String, String>>> getServicesData() {System.out.println("Received request for combined service data");return commonService.callServicesAsync().doOnSuccess(response -> System.out.println("Successfully retrieved data: " + response)).doOnError(error -> System.err.println("Error occurred while fetching service data: " + error.getMessage()));}
}

测试接口 

结语 

WebClient 是一个功能强大且灵活的非阻塞 HTTP 客户端,特别适合在高并发和响应式编程场景下使用,是替代传统 RestTemplate 的优秀选择。在实际项目中,通过合理配置(如超时、连接池)和优化(如负载均衡、重试机制),可以显著提高服务间通信的效率和可靠性,降低延迟和资源消耗。

同时,结合 Spring WebFlux 提供的响应式编程支持,WebClient 能够更好地应对微服务架构中复杂的通信需求,成为开发现代分布式系统的重要工具。

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

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

相关文章

QML学习 —— 29、3种不同使用动画的方式(附源码)

效果 说明 第一种:属性动画 - 当启动软件时候自动执行动画。      第二种:行为动画 - 当属性发生变化则自动执行动画。      第三种:目标动画 - 将动画变为对象,指定对象的目标进行执行动画。 代码 import QtQuick 2.12 import QtQuick.Window 2.12 import QtQu…

Redis缓存穿透及常见的解决方案

一.什么是缓存穿透&#xff1f; 缓存穿透是指当客户端请求的数据在缓存&#xff08;如 Redis&#xff09;中不存在&#xff0c;并且在数据库中也不存在时&#xff0c;直接绕过缓存去请求数据库。这种情况会导致&#xff1a; 缓存系统无法发挥作用&#xff0c;数据每次都会直接…

(原创)Android Studio新老界面UI切换及老版本下载地址

前言 这两天下载了一个新版的Android Studio&#xff0c;发现整个界面都发生了很大改动&#xff1a; 新的界面的一些设置可参考一些博客&#xff1a; Android Studio新版UI常用设置 但是对于一些急着开发的小伙伴来说&#xff0c;没有时间去适应&#xff0c;那么怎么办呢&am…

windows下安装wsl的ubuntu,同时配置深度学习环境

写在前面&#xff0c;本次文章只是个人学习记录&#xff0c;不具备教程的作用。个别信息是网上的&#xff0c;我会标注&#xff0c;个人是gpt生成的 安装wsl 直接看这个就行&#xff1b;可以不用备份软件源。 https://blog.csdn.net/weixin_44301630/article/details/1223900…

Node.js的http模块:创建HTTP服务器、客户端示例

新书速览|Vue.jsNode.js全栈开发实战-CSDN博客 《Vue.jsNode.js全栈开发实战&#xff08;第2版&#xff09;&#xff08;Web前端技术丛书&#xff09;》(王金柱)【摘要 书评 试读】- 京东图书 (jd.com) 要使用http模块&#xff0c;只需要在文件中通过require(http)引入即可。…

AI赋能电商:构建高效、智能化的新零售生态

随着人工智能&#xff08;AI&#xff09;技术的不断进步&#xff0c;其在电商领域的应用日益广泛&#xff0c;从购物推荐到供应链管理&#xff0c;再到商品定价&#xff0c;AI正在全面改变传统电商的运营模式&#xff0c;并推动行业向智能化和精细化方向发展。本文将探讨如何利…

算法之区间和题目讲解

题干 难度&#xff1a;简单 题目分析 题目要求算出每个指定区间内元素的总和。 然而&#xff0c;区间在输入的最下面&#xff0c;所以按照暴力破解的思路&#xff0c;我们首先要遍历数组&#xff0c;把它的值都存进去。 然后&#xff0c;遍历下面的区间&#xff0c;从索引a…

openssl颁发包含主题替代名的证书–SAN

原文地址&#xff1a;openssl颁发包含主题替代名的证书–SAN – 无敌牛 欢迎参观我的个人博客&#xff1a;无敌牛 – 技术/著作/典籍/分享等 在 X.509 证书中&#xff0c;commonName&#xff08;CN&#xff09;字段只能有一个值。如果让证书支持多个域名和IP地址&#xff0c;…

从尾到头打印链表 剑指offer

题目描述 输入一个链表的头节点&#xff0c;从尾到头反过来打印出每个节点的值。 链表节点定义如下&#xff1a; struct ListNode {int m_nKey;ListNode*m_pNext; }; 代码实现 栈实现&#xff1a; 递归实现&#xff1a; 但是用递归实现可能存在的问题&#xff1a;

ajax基础

一&#xff1a;express框架 在终端输入nodejs文件名 // 引入express const express require(express); //创建应用对象 const app express(); //创建路由规则 app.get(/,(request,response) > {//设置响应response.send(Hello Express); }); // 监听3000端口 app.lis…

免费实用在线AI工具集合 - 加菲工具

免费在线工具-加菲工具 https://orcc.online/ 在线录屏 https://orcc.online/recorder 时间戳转换 https://orcc.online/timestamp Base64 编码解码 https://orcc.online/base64 URL 编码解码 https://orcc.online/url Hash(MD5/SHA1/SHA256…) 计算 https://orcc.online/h…

UE5肉鸽游戏教程学习

学习地址推荐&#xff1a;UE5肉鸽项目实战教程_哔哩哔哩_bilibili

101页PDF | 德勤_XX集团信息化顶层规划设计信息化总体解决方案(限免下载)

一、前言 这份报告是一份关于集团信息化顶层规划设计的总体解决方案&#xff0c;旨在通过信息化转型提升集团管控和企业运营效率。报告回顾了项目的背景、目标和工作过程&#xff0c;分析了集团面临的内部和外部挑战&#xff0c;并提出了一系列解决方案&#xff0c;包括自上而…

L14.【LeetCode笔记】返回倒数第k个节点

目录 1.题目 2.分析 思路 代码 提交结果 1.题目 面试题 02.02. 返回倒数第 k 个节点 实现一种算法&#xff0c;找出单向链表中倒数第 k 个节点。返回该节点的值。 注意&#xff1a;本题相对原题稍作改动 示例&#xff1a; 输入&#xff1a; 1->2->3->4->5 和 …

【zookeeper03】消息队列与微服务之zookeeper集群部署

ZooKeeper 集群部署 1.ZooKeeper 集群介绍 ZooKeeper集群用于解决单点和单机性能及数据高可用等问题。 集群结构 Zookeeper集群基于Master/Slave的模型 处于主要地位负责处理写操作)的主机称为Leader节点&#xff0c;处于次要地位主要负责处理读操作的主机称为 follower 节点…

STM32端口模拟编码器输入

文章目录 前言一、正交编码器是什么&#xff1f;二、使用步骤2.1开启时钟2.2配置编码器引脚 TIM3 CH1(PA6) CH2 (PA7)上拉输入2.3.初始化编码器时基2.4 初始化编码器输入2.5 配置编码器接口2.6 开启定时器2.7获取编码器数据 三、参考程序四、测试结果4.1测试方法4.2串口输出结果…

【Mybatis】@Param注解 resultMap手动映射

文章目录 一、映射文件参数二、查询映射2-1 一对一2-2 一对多2-3 总结 一、映射文件参数 Param 注解官方文档解释 1、单个参数&#xff08;对象&#xff09;不使用注解 public int save(User user);<!-- 添加用户 --> <insert id"save" parameterType&quo…

正则表达式灾难:重新认识“KISS原则”的意义

RSS Feed 文章标题整理 微积分在生活中的应用与思维启发 捕鹿到瞬时速度的趣味探索 微积分是一扇通往更广阔世界的门&#xff0c;从生活中学习思维的工具。 数据库才是最强架构 你还在被“复杂架构”误导吗&#xff1f; 把业务逻辑写入数据库&#xff0c;重新定义简单与效率。…

C#基础上机练习题

21.计算500-800区间内素数的个数cn&#xff0c;并按所求素数的值从大到小的顺序排列&#xff0c;再计算其间隔加、减之和&#xff0c;即第1个素数-第2个素数第3个素数-第4个素数第5个素数……的值sum。请编写函数实现程序的要求&#xff0c;把结果cn和sum输出。 22.在三位整数…

【STM32】在 STM32 USB 设备库添加新的设备类

说实话&#xff0c;我非常想吐槽 STM32 的 USB device library&#xff0c;总感觉很混乱。 USB Device library architecture 根据架构图&#xff1a; Adding a custom class 如果你想添加新的设备类&#xff0c;必须修改的文件有 usbd_desc.cusbd_conf.cusb_device.c 需要…