掌握 Spring 中的 WebClient

在开发 Spring Boot 应用程序时经常需要与其他 Web 服务进行通信。过去,开发人员通常使用 RestTemplate 来实现这一目的。然而,随着响应式编程的出现以及对更高效资源利用的需求,WebClient 已成为更优选择。

WebClient 是 Spring WebFlux 框架引入的非阻塞响应式 Web 客户端。它旨在支持异步和流式场景,非常适合需要高并发和可扩展性的应用程序。

响应式应用

在开发响应式应用程序时,WebClient 是首选。响应式编程旨在通过利用非阻塞 I/O 高效处理大量并发请求。在以下情况下优先使用 WebClient:

  • 响应式 API:如果您的应用程序使用 Reactor、RxJava 或其他响应式框架,WebClient 可以无缝集成。

  • 事件驱动架构:依赖事件的系统,如物联网平台。

示例:

java 复制代码
public Mono<User> fetchUser(String userId) {
    return WebClient.create()
           .get()
           .uri("https://api.example.com/users/{id}", userId)
           .retrieve()
           .bodyToMono(User.class);
}

微服务通信

在微服务架构中,服务之间经常需要相互通信。WebClient 实现高效、高吞吐量的服务间通信。它允许:

  • 并发请求:同时发送多个请求而不阻塞线程。

  • 低延迟:以更短的响应时间处理实时数据。

示例:

java 复制代码
public Flux<Order> fetchUserOrders(String userId) {
    return WebClient.create()
           .get()
           .uri("https://orderservice.com/orders?userId=" + userId)
           .retrieve()
           .bodyToFlux(Order.class);
}

流式和实时数据

WebClient 在处理流式数据和服务器发送事件(SSE)方面表现出色。对于需要以下功能的应用程序使用 WebClient:

  • 数据流:例如,消费实时股票价格更新或传感器数据。

  • 长连接:处理 WebSockets 或 SSE,如聊天或实时仪表板应用。

示例:

java 复制代码
public Flux<StockPrice> streamStockPrices() {
    return WebClient.create()
           .get()
           .uri("https://api.example.com/stock-prices/stream")
           .retrieve()
           .bodyToFlux(StockPrice.class);
}

处理大负载

处理大文件上传/下载或流式传输大数据集的应用程序应使用 WebClient,因为它能高效利用资源:

  • 由于其非阻塞 I/O,可实现高效内存处理。

  • 支持流式传输数据块,而无需将整个内容加载到内存中。

示例:

java 复制代码
public Flux<DataChunk> downloadLargeFile() {
    return WebClient.create()
           .get()
           .uri("https://api.example.com/largefile")
           .retrieve()
           .bodyToFlux(DataChunk.class);
}

重试、熔断、超时

WebClient 与 Resilience4j 等库集成,提供自动失败重试、熔断、超时控制的能力:

示例:

java 复制代码
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import reactor.core.publisher.Mono;

CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("myService");

public Mono<User> fetchUserWithResilience(String userId) {
    return WebClient.create()
           .get()
           .uri("https://api.example.com/users/{id}", userId)
           .retrieve()
           .bodyToMono(User.class)
           .transformDeferred(CircuitBreakerOperator.of(circuitBreaker));
}

身份认证

WebClient 也提供支持安全的通信方式:

  • OAuth2 集成:与 Spring Security 配合使用,处理 OAuth2 令牌管理。

  • 自定义身份验证:配置自定义标头或令牌以进行安全通信。

示例:

java 复制代码
public Mono<User> fetchUserWithToken(String userId, String token) {
    return WebClient.builder()
           .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token)
           .build()
           .get()
           .uri("https://api.example.com/users/{id}", userId)
           .retrieve()
           .bodyToMono(User.class);
}

MOCK API

WebClient 适合用于测试,因为它与 WireMock 等模拟服务器集成:

  • 模拟 API 响应以进行集成测试。

  • 测试超时或错误代码等失败场景。

示例:

java 复制代码
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

importstatic com.github.tomakehurst.wiremock.client.WireMock.*;
importstatic org.junit.jupiter.api.Assertions.*;

publicclass WebClientTest {

    @Test
    public void testFetchUser() {
        WireMockServer wireMockServer = new WireMockServer();
        wireMockServer.start();

        wireMockServer.stubFor(get(urlEqualTo("/users/1"))
               .willReturn(aResponse()
                       .withHeader("Content-Type", "application/json")
                       .withBody("{\"id\":1,\"name\":\"John Doe\"}")));

        WebClient webClient = WebClient.create(wireMockServer.baseUrl());
        Mono<User> user =
                webClient.get().uri("/users/1").retrieve().bodyToMono(User.class);

        StepVerifier.create(user)
               .expectNextMatches(u -> u.getName().equals("John Doe"))
               .verifyComplete();

        wireMockServer.stop();
    }
}

WebClient 与 RestTemplate 的对比

WebClient 的优势

  • 非阻塞 I/O:WebClient 使用非阻塞模型,这意味着在等待响应时线程不会被阻塞。当同时进行多个 API 调用时,这一点特别有用。

  • 支持响应式流:WebClient 与 Reactor 和 RxJava 等响应式库无缝集成,适用于现代响应式架构。

  • 更好的可扩展性:非阻塞行为允许 WebClient 同时处理更多请求,而不会耗尽服务器线程。

  • 更强大:WebClient 更灵活且功能丰富,支持高级用例,如流式传输大文件、处理 WebSocket 连接。

调用接口

使用 RestTemplate:

java 复制代码
import org.springframework.web.client.RestTemplate;

public class RestTemplateExample {

    private RestTemplate restTemplate = new RestTemplate();

    public String getUserDetails(String userId) {
        String url = "https://api.example.com/users/" + userId;
        return restTemplate.getForObject(url, String.class);
    }
}

使用 WebClient:

java 复制代码
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

publicclass WebClientExample {

    private WebClient webClient = WebClient.create();

    public Mono<String> getUserDetails(String userId) {
        String url = "https://api.example.com/users/" + userId;
        return webClient.get()
               .uri(url)
               .retrieve()
               .bodyToMono(String.class);
    }
}

区别:

  • RestTemplate 会阻塞,直到 API 调用完成。

  • WebClient 返回一个 Mono,允许应用程序在等待响应时处理其他任务。

并发调用

使用 RestTemplate:

java 复制代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

publicclass RestTemplateConcurrentExample {

    private RestTemplate restTemplate = new RestTemplate();

    public void fetchMultipleUsers(String[] userIds) {
        ExecutorService executor = Executors.newFixedThreadPool(userIds.length);
        for (String userId : userIds) {
            executor.submit(() -> {
                String url = "https://api.example.com/users/" + userId;
                String response = restTemplate.getForObject(url, String.class);
                System.out.println(response);
            });
        }
        executor.shutdown();
    }
}

使用 WebClient:

java 复制代码
import reactor.core.publisher.Flux;

public class WebClientConcurrentExample {

    private WebClient webClient = WebClient.create();

    public Flux<String> fetchMultipleUsers(String[] userIds) {
        return Flux.fromArray(userIds)
               .flatMap(userId -> webClient.get()
                       .uri("https://api.example.com/users/" + userId)
                       .retrieve()
                       .bodyToMono(String.class));
    }
}

区别:

  • RestTemplate 需要显式管理线程,增加了复杂性。

  • WebClient 非常方便地处理并发,减少了样板代码。

相关推荐
一只叫煤球的猫5 小时前
写代码很6,面试秒变菜鸟?不卖课,面试官视角走心探讨
前端·后端·面试
bobz9655 小时前
tcp/ip 中的多路复用
后端
bobz9655 小时前
tls ingress 简单记录
后端
皮皮林5517 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友7 小时前
什么是OpenSSL
后端·安全·程序员
bobz9657 小时前
mcp 直接操作浏览器
后端
前端小张同学9 小时前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook9 小时前
Manim实现闪光轨迹特效
后端·python·动效
武子康10 小时前
大数据-98 Spark 从 DStream 到 Structured Streaming:Spark 实时计算的演进
大数据·后端·spark
该用户已不存在10 小时前
6个值得收藏的.NET ORM 框架
前端·后端·.net