1. 概述
本文将深入探讨 Spring 5 引入的响应式 Web 客户端 WebClient,以及专为测试设计的 WebTestClient。我们将通过实际场景分析其核心特性与最佳实践。
2. 什么是 WebClient?
简单来说,WebClient 是执行 Web 请求的统一入口点。作为 Spring Web Reactive 模块的核心组件,它将逐步取代传统的 RestTemplate。关键特性包括:
✅ 响应式非阻塞设计:基于 HTTP/1.1 协议
✅ 双模式支持:既适用于响应式栈(Reactive Stack),也兼容 Servlet 栈(通过阻塞操作获取结果)
⚠️ 注意:在响应式环境中应避免阻塞操作,否则违背设计初衷
其唯一实现类是 DefaultWebClient,后续示例均基于此展开。
3. 依赖配置
3.1. Maven 配置
在 pom.xml
添加核心依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
3.2. Gradle 配置
在 build.gradle
中添加:
dependencies {
compile 'org.springframework.boot:spring-boot-starter-webflux'
}
4. WebClient 实战指南
掌握 WebClient 需理解三个核心环节:实例创建 → 请求构建 → 响应处理
4.1. 创建 WebClient 实例
提供三种创建方式:
方式一:默认配置
WebClient client = WebClient.create();
方式二:指定基础 URI
WebClient client = WebClient.create("http://localhost:8080");
方式三:高级定制(推荐)
WebClient client = WebClient.builder()
.baseUrl("http://localhost:8080")
.defaultCookie("cookieKey", "cookieValue")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultUriVariables(Collections.singletonMap("url", "http://localhost:8080"))
.build();
4.2. 配置超时参数
默认 30 秒超时往往不满足需求,需自定义 HttpClient:
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.responseTimeout(Duration.ofMillis(5000))
.doOnConnected(conn ->
conn.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS))
.addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS)));
WebClient client = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
⚠️ 踩坑提示:timeout()
方法设置的是信号超时(Mono/Flux 发布超时),而非 HTTP 连接/读写超时!
4.3. 定义请求方法
两种方式指定 HTTP 方法:
通用方式
UriSpec<RequestBodySpec> uriSpec = client.method(HttpMethod.POST);
快捷方法
UriSpec<RequestBodySpec> uriSpec = client.post();
❌ 注意:请求规范对象(如 UriSpec
)不可复用!每次请求需重新创建。
4.4. 定义请求 URL
三种 URL 设置方式:
直接字符串
RequestBodySpec bodySpec = uriSpec.uri("/resource");
UriBuilder 函数式
RequestBodySpec bodySpec = uriSpec.uri(
uriBuilder -> uriBuilder.pathSegment("/resource").build());
URI 对象
RequestBodySpec bodySpec = uriSpec.uri(URI.create("/resource"));
4.5. 定义请求体
四种主流请求体设置方式:
方式一:直接值
RequestHeadersSpec<?> headersSpec = bodySpec.bodyValue("data");
方式二:Publisher
RequestHeadersSpec<?> headersSpec = bodySpec.body(
Mono.just(new Foo("name")), Foo.class);
方式三:BodyInserters(值)
RequestHeadersSpec<?> headersSpec = bodySpec.body(
BodyInserters.fromValue("data"));
方式四:BodyInserters(Publisher)
RequestHeadersSpec headersSpec = bodySpec.body(
BodyInserters.fromPublisher(Mono.just("data")),
String.class);
高级场景:文件上传
LinkedMultiValueMap map = new LinkedMultiValueMap();
map.add("key1", "value1");
map.add("key2", "value2");
RequestHeadersSpec<?> headersSpec = bodySpec.body(
BodyInserters.fromMultipartData(map));
核心概念:
BodyInserter
负责填充响应体,Publisher
(如 Mono/Flux)提供数据流。
4.6. 定义请求头
设置头信息会与实例化时的默认值合并:
ResponseSpec responseSpec = headersSpec.header(
HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
.acceptCharset(StandardCharsets.UTF_8)
.ifNoneMatch("*")
.ifModifiedSince(ZonedDateTime.now())
.retrieve();
4.7. 获取响应
两种响应处理模式:
模式一:完整响应控制(exchangeToMono/exchangeToFlux)
Mono<String> response = headersSpec.exchangeToMono(response -> {
if (response.statusCode().equals(HttpStatus.OK)) {
return response.bodyToMono(String.class);
} else if (response.statusCode().is4xxClientError()) {
return Mono.just("Error response");
} else {
return response.createException()
.flatMap(Mono::error);
}
});
模式二:直接获取响应体(retrieve)
Mono<String> response = headersSpec.retrieve()
.bodyToMono(String.class);
⚠️ 注意:retrieve()
在 4xx/5xx 状态码时会抛出 WebClientException
!
5. WebTestClient 测试实战
WebTestClient 是测试 WebFlux 接口的利器,API 与 WebClient 高度相似。
5.1. 绑定真实服务器
端到端集成测试:
WebTestClient testClient = WebTestClient
.bindToServer()
.baseUrl("http://localhost:8080")
.build();
5.2. 绑定路由函数
测试特定 RouterFunction:
RouterFunction function = RouterFunctions.route(
RequestPredicates.GET("/resource"),
request -> ServerResponse.ok().build()
);
WebTestClient
.bindToRouterFunction(function)
.build().get().uri("/resource")
.exchange()
.expectStatus().isOk()
.expectBody().isEmpty();
5.3. 绑定 Web 处理器
WebHandler handler = exchange -> Mono.empty();
WebTestClient.bindToWebHandler(handler).build();
5.4. 绑定应用上下文
自动扫描控制器和 @EnableWebFlux
配置:
@Autowired
private ApplicationContext context;
WebTestClient testClient = WebTestClient.bindToApplicationContext(context)
.build();
5.5. 绑定控制器
直接注入控制器测试:
@Autowired
private Controller controller;
WebTestClient testClient = WebTestClient.bindToController(controller).build();
5.6. 执行测试请求
典型测试链式调用:
WebTestClient
.bindToServer()
.baseUrl("http://localhost:8080")
.build()
.post()
.uri("/resource")
.exchange()
.expectStatus().isCreated()
.expectHeader().valueEquals("Content-Type", "application/json")
.expectBody().jsonPath("field").isEqualTo("value");
6. 总结
WebClient 作为 Spring 5 的响应式 HTTP 客户端,通过以下优势革新了客户端请求处理:
- 统一编程模型:同步/异步双模式支持
- 非阻塞架构:基于 Reactor 响应式流
- 灵活配置:超时/拦截器/编解码器深度定制
- 测试友好:WebTestClient 提供专用测试 API
完整代码示例见 GitHub 仓库。