响应式客户端 WebClient详解

1. WebClient 简介

1.1 什么是 WebClient

  • WebClient 是 Spring 5 引入的响应式 HTTP 客户端,基于 Reactor 实现非阻塞 I/O。支持异步、非阻塞式调用,适用于微服务、API 网关等高并发场景。它是 RestTemplate 的响应式版,能更好地支持响应式编程和流式数据处理。

  • 核心特性:

    • 非阻塞、异步 HTTP 请求

    • 支持 Mono(0..1)和 Flux(0..N)响应流

    • 内置 JSON、XML、Form 编解码器

    • Filter 链机制支持拦截、认证、日志等

1.2 WebClient 与 RestTemplate 区别

特性 RestTemplate WebClient
阻塞 否(非阻塞)
响应式 是(Mono/Flux)
并发 线程池控制 背压机制 + 连接池
SSE/流式 不支持 支持
编码/解码 手动/自动 自动支持 JSON/XML/Form

1.3 适用场景

  • 微服务之间非阻塞调用

  • 高并发 HTTP 请求

  • 流式数据/Server-Sent Events (SSE)

  • 响应式编程体系(WebFlux、RSocket)


2. 快速开始

2.1 依赖引入

Maven:

<dependency>

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

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

</dependency>

Gradle:

implementation 'org.springframework.boot:spring-boot-starter-webflux'

2.2 创建 WebClient 实例

复制代码
// 简单创建
WebClient client = WebClient.create("https://api.example.com");

// Builder 创建
WebClient client = WebClient.builder()
        .baseUrl("https://api.example.com")
        .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
        .build();

2.3 基本 GET/POST 请求示例

java 复制代码
// GET 请求
Mono<String> response = client.get()
        .uri("/users/{id}", 1)
        .retrieve()
        .bodyToMono(String.class);

// POST 请求
Mono<String> postResponse = client.post()
        .uri("/users")
        .bodyValue(new User("Alice", 20))
        .retrieve()
        .bodyToMono(String.class);

3. 请求构建详解

3.1 URI 构建与参数传递

java 复制代码
// Path Variable
client.get().uri("/users/{id}", 123).retrieve().bodyToMono(User.class);

// Query 参数
client.get().uri(uriBuilder -> uriBuilder.path("/users")
        .queryParam("page", 1)
        .queryParam("size", 10)
        .build())
    .retrieve()
    .bodyToMono(UserList.class);

3.2 Header 设置

java 复制代码
client.get()
    .uri("/users/1")
    .header("Authorization", "Bearer token")
    .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
    .retrieve()
    .bodyToMono(User.class);

3.3 Body 构建

  • JSON
java 复制代码
client.post()
    .uri("/users")
    .bodyValue(new User("Alice", 20))
    .retrieve()
    .bodyToMono(String.class);
  • Form
java 复制代码
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("username", "alice");
formData.add("password", "123456");

client.post()
    .uri("/login")
    .contentType(MediaType.APPLICATION_FORM_URLENCODED)
    .bodyValue(formData)
    .retrieve()
    .bodyToMono(String.class);
  • 文件上传
java 复制代码
MultiValueMap<String, Object> data = new LinkedMultiValueMap<>();
data.add("file", new FileSystemResource("test.txt"));

client.post()
    .uri("/upload")
    .contentType(MediaType.MULTIPART_FORM_DATA)
    .body(BodyInserters.fromMultipartData(data))
    .retrieve()
    .bodyToMono(String.class);

4. 响应处理

4.1 Mono 与 Flux 基础

  • Mono<T> → 0..1 个元素

  • Flux<T> → 0..N 个元素(流式响应)

4.2 bodyToMono 与 bodyToFlux

java 复制代码
Mono<User> monoUser = client.get().uri("/user/1").retrieve().bodyToMono(User.class);
Flux<User> fluxUsers = client.get().uri("/users").retrieve().bodyToFlux(User.class);

5. 错误与异常处理

5.1 onStatus 用法

java 复制代码
client.get()
    .uri("/users/999")
    .retrieve()
    .onStatus(HttpStatus::is4xxClientError, resp -> Mono.error(new RuntimeException("Client Error")))
    .onStatus(HttpStatus::is5xxServerError, resp -> Mono.error(new RuntimeException("Server Error")))
    .bodyToMono(User.class);

5.2 doOnError、retry 等响应式异常处理

java 复制代码
client.get()
    .uri("/slow-api")
    .retrieve()
    .bodyToMono(String.class)
    .doOnError(e -> log.error("Request failed", e))
    .retry(3)
    .timeout(Duration.ofSeconds(2));

6. 高级用法

6.1 连接池与超时配置

java 复制代码
ConnectionProvider provider = ConnectionProvider.builder("custom")
        .maxConnections(50)
        .maxIdleTime(Duration.ofSeconds(30))
        .build();

HttpClient httpClient = HttpClient.create(provider)
        .responseTimeout(Duration.ofSeconds(5));

WebClient client = WebClient.builder()
        .clientConnector(new ReactorClientHttpConnector(httpClient))
        .build();

6.2 拦截器与过滤器(ExchangeFilterFunction)

java 复制代码
WebClient.builder()
    .filter((request, next) -> {
        System.out.println("Request: " + request.method() + " " + request.url());
        return next.exchange(request);
    })
    .build();

6.3 OAuth2 / 认证集成

java 复制代码
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
    new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepository, authorizedClientRepository);

WebClient client = WebClient.builder()
    .apply(oauth.oauth2Configuration())
    .build();

6.4 SSE / 流式响应处理

java 复制代码
Flux<ServerSentEvent<String>> events = client.get()
    .uri("/sse/stream")
    .retrieve()
    .bodyToFlux(new ParameterizedTypeReference<ServerSentEvent<String>>() {});

events.subscribe(e -> System.out.println(e.data()));

7. 性能优化与最佳实践

  • 单例复用 WebClient,避免重复创建连接池

  • 取消订阅 / 资源释放Disposable.dispose()

  • 大文件 / 流式下载bodyToFlux(DataBuffer.class) 配合 DataBufferUtils.write

  • 日志与调试技巧:HttpClient.create().wiretap(true) // Netty 级别日志


8. 测试与 Mock

8.1 单元测试(StepVerifier)

java 复制代码
Mono<User> monoUser = client.get().uri("/user/1").retrieve().bodyToMono(User.class);

StepVerifier.create(monoUser)
    .expectNextMatches(user -> user.getName().equals("Alice"))
    .verifyComplete();

8.2 MockWebServer 使用

  • 可用 okhttp3.mockwebserver.MockWebServer 模拟 HTTP 服务

  • 配合 WebClient 测试请求、响应和异常

8.3 WebTestClient

复制代码
webTestClient.get().uri("/users/1")
            .exchange()
            .expectStatus().isOk()
            .expectBody(User.class)
            .value(user -> assertThat(user.getName()).isEqualTo("Tom"));

9. 常见问题与解决方案

  • 超时问题 → 配置 responseTimeout.timeout(Duration)

  • 连接数限制 → 调整 ConnectionProvider.maxConnections

  • 兼容性注意事项 → WebClient 默认 Reactor Netty,需确保服务端支持 HTTP/1.1 或 HTTP/2


10. 实战案例

10.1 与 Spring Cloud 集成

java 复制代码
@Autowired
private WebClient.Builder webClientBuilder;

Mono<String> result = webClientBuilder.build()
        .get()
        .uri("http://user-service/users/1")
        .retrieve()
        .bodyToMono(String.class);

10.2 微服务间调用

  • WebClient + Ribbon / LoadBalancerClient 或 Spring Cloud LoadBalancer

  • 支持异步并发请求、聚合结果


11. 参考资料与扩展阅读

相关推荐
北执南念2 小时前
基于 Spring 的策略模式框架,用于根据不同的类的标识获取对应的处理器实例
java·spring·策略模式
王道长服务器 | 亚马逊云2 小时前
一个迁移案例:从传统 IDC 到 AWS 的真实对比
java·spring boot·git·云计算·github·dubbo·aws
华仔啊2 小时前
为什么 keySet() 是 HashMap 遍历的雷区?90% 的人踩过
java·后端
9号达人2 小时前
Java 13 新特性详解与实践
java·后端·面试
橙序员小站2 小时前
搞定系统设计题:如何设计一个支付系统?
java·后端·面试
Java水解2 小时前
Spring Security6.3.x使用指南
后端·spring
嘟嘟可在哪里。3 小时前
IntelliJ IDEA git凭据帮助程序
java·git·intellij-idea
岁忧3 小时前
(LeetCode 每日一题) 3541. 找到频率最高的元音和辅音 (哈希表)
java·c++·算法·leetcode·go·散列表
_extraordinary_3 小时前
Java 多线程进阶(四)-- 锁策略,CAS,synchronized的原理,JUC当中常见的类
java·开发语言