一、介绍
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 :链式调用,天然可组合
retry、timeout、filter等,代码更短、更直观 - 协议全覆盖:封装 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?