WebClient 简述

一、介绍

1、WebClient 是什么?

  • 定义:Spring 5 引入的响应式 HTTP 客户端,位于 spring-webflux 模块
  • 作用:替代老旧的 RestTemplate(Spring 6 起 RestTemplate 已处于"维护模式")
  • 编程风格:Reactive(Reactor Flux / Mono),也支持同步阻塞式调用
  • 底层:Netty(默认),也可切换为 Jetty Reactor、Apache HttpComponents 等

2、为什么选 WebClient?

  • 响应式:高并发、少线程、低延迟,在 IO 密集场景下的 QPS 更高,同步阻塞场景也能用
  • 流式 API :链式调用,天然可组合 retrytimeoutfilter 等,代码更短、更直观
  • 协议全覆盖:封装 HTTP/1.1、HTTP/2、WebSocket、SSE 等多种协议
  • 无缝集成:Spring Cloud、Spring Security、Spring Data R2DBC 等全家桶链路生态

二、使用方法

1、引入依赖

正如前文所言,WebClient 位于 spring-webflux 模块,因此只要 webflux starter 即可。

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

2、创建 WebClient 客户端实例

(1)全局共享

写一个配置类,把 WebClient 注册成单例 Bean。整个应用只需创建一次,任何地方都可以直接 @Autowired 拿来用,这样既省资源又方便统一配置。配置类的代码示例如下:

java 复制代码
@Configuration
public class WebClientConfig {

    @Bean                     // 全局唯一实例,Spring 会把该对象放进容器,任何地方都能注入
    public WebClient webClient() {
        return WebClient.builder()
                .baseUrl("https://httpbin.org")   // 公共前缀,可省
                .defaultHeader(HttpHeaders.USER_AGENT, "demo-app/1.0")
                .build();
    }
}

(2)临时创建

在代码里手动创建一个 WebClient 实例,不交给 Spring 容器托管,也不共享,代码示例如下:

java 复制代码
WebClient client = WebClient.create("https://httpbin.org");

(3)资源管理

  • 全局共享(@Bean)的 WebClient,在 Spring 容器关闭时会连带清理,无需手动关闭
  • 临时创建的 WebClient 用完即丢,也无需手动关闭

3、发送请求

(1)GET(阻塞式)

当前线程会卡住,直到获取响应结果。

java 复制代码
String body = client.get()
        .uri("/get?name=Tom")
        .retrieve()
        .bodyToMono(String.class)
        .block();          // 阻塞点
        
System.out.println("阻塞结果 = " + body);

(2)GET(异步回调)

当前线程执行 subscribe 后就可以去做别的事,获取响应结果后回调执行后续逻辑。

java 复制代码
client.get()
      .uri("/get?name=Jerry")
      .retrieve()
      .bodyToMono(String.class)
      .subscribe(
              json -> System.out.println("异步回调 = " + json),   // 成功
              err  -> err.printStackTrace()                      // 失败
      );
      
System.out.println("主线程继续执行,不等待");

(3)GET(流式)

适用于服务端持续推送数据的场景,如 SSE、大文件、日志流。这里演示 HTTP 长连接 + 无限行 JSON 的流式消费,代码示例如下:

java 复制代码
// 假定 httpbin.org 的 /stream/{n} 会一次返回 n 行 JSON
Flux<String> flux = client.get()
        .uri("/stream/10")          // 10 行数据
        .retrieve()
        .bodyToFlux(String.class);  // 每收到一行就往下游发

flux.doOnNext(line -> System.out.println("收到流 => " + line))
    .subscribe();

(4)POST JSON

把 Java 对象序列化成 JSON 发给服务器。.bodyValue() 会将常见类型自动序列化,如 POJO、基本类型、Map、List 和任意 Collection。

java 复制代码
Mono<String> resp = client.post()
        .uri("/post")
        .contentType(MediaType.APPLICATION_JSON)
        .bodyValue(new User("Tom", 18))     // 自动序列化成 JSON
        .retrieve()
        .bodyToMono(String.class);

resp.subscribe(json -> System.out.println("POST-JSON 返回 => " + json));

(5)POST multipart/form-data

这样可以同时上传文件 + 文本,代码示例如下:

java 复制代码
// 1. 准备 multipart 主体
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("file", new FileSystemResource("demo.txt"));  // 文件
builder.part("desc", "这是说明文字");                       // 普通字段

// 2. 发送请求
Mono<String> resp = client.post()
        .uri("/post")
        .contentType(MediaType.MULTIPART_FORM_DATA)
        .body(BodyInserters.fromMultipartData(builder.build()))
        .retrieve()
        .bodyToMono(String.class);

resp.subscribe(json -> System.out.println("POST-Multipart 返回 => " + json));

4、语法解释

下面对 WebClient 链式调用里最常见的 5 个方法进行解释:

(1)uri

uri(String path, Object... vars) 是剩余路径,每次请求都可变,会自动拼在前缀 baseUrl 的后面,最终 URL = baseUrl + uri。代码示例如下:

java 复制代码
.uri("/user/{id}", 123)   // -> /user/123

(2)retrieve

retrieve() 是一个无参方法,负责把请求发出去 并拿到完整的 HTTP 响应。默认 4xx/5xx 会抛 WebClientResponseException,如果想自己处理错误,需要使用 exchangeToMono 或 onStatus。

(3)bodyToMono

bodyToMono(Class<T> clazz) 会将整个响应体一次性转换成单个对象(Mono),代码示例如下:

java 复制代码
.bodyToMono(User.class)     // JSON -> User 对象
.bodyToMono(String.class)   // 原始文本

(4)bodyToFlux

bodyToFlux(Class<T> clazz) 会将持续流切成多个对象(Flux),如SSE、大文件分块等场景,代码示例如下:

java 复制代码
.bodyToFlux(String.class);   // 每收到一行就变成一个 String

(5)subscribe

subscribe(Consumer<T> onNext, Consumer<Throwable> onError) 会执行整个链式调用,并提供收到数据/出错时的回调。如果不调用 subscribe() 或 block(),整个链式调用不会真正执行。

java 复制代码
.subscribe(
    user -> log.info("收到用户 {}", user),
    ex   -> log.error("请求失败", ex)
)

三、剩余的问题

因为懒惰和大大小小的事情,这一篇拖了很久,久到我都忘记了。以下是剩余的问题,原本是拟定作为一级标题进行学习的,只有暂且搁置,以后有机会再进行补习。

  • 什么是响应式?
  • 什么是 Content-Type?
  • 什么是 Mono?
  • 什么是 Flux?
相关推荐
狗头大军之江苏分军3 小时前
请不要在感情里丢掉你的“我”
前端·后端
wudl55663 小时前
JDK 21性能优化详解
java·开发语言·性能优化
CodeAmaz3 小时前
ELK(Elasticsearch + Logstash + Kibana + Filebeat)采集方案
java·elk·elasticsearch·1024程序员节
864记忆3 小时前
项目名称:烟酒进销存管理系统
java
纪莫3 小时前
技术面:SpringBoot(启动流程、如何优雅停机)
java·spring·java面试⑧股
BingoGo3 小时前
2025 年必须尝试的 5 个 Laravel 新特性
后端
豆浆Whisky3 小时前
掌握Go context:超越基础用法的正确实践模式|Go语言进阶(13)
后端·go
用户68545375977694 小时前
📁 设计一个文件上传和存储服务:云盘的秘密!
后端
Merrick4 小时前
亲手操作Java抽象语法树
java·后端