1. 简介
WebClient是Spring 5引入的响应式Web客户端,用于执行HTTP请求。相比传统的RestTemplate,WebClient提供了非阻塞、响应式的方式来处理HTTP请求,是Spring推荐的新一代HTTP客户端工具。本文将详细介绍如何在SpringBoot 3.x中配置和使用WebClient。
2. 环境准备
2.1 依赖配置
在pom.xml
中添加必要的依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.10</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
3. WebClient配置
3.1 基础配置
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient() {
return WebClient.builder()
.baseUrl("https://echo.apifox.com")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.build();
}
}
3.2 高级配置
package com.coderjia.boot3webflux.config;
import io.netty.channel.ChannelOption;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;
import java.time.Duration;
/**
* @author CoderJia
* @create 2024/12/3 下午 09:42
* @Description
**/
@Slf4j
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient() {
// 配置HTTP连接池
ConnectionProvider provider = ConnectionProvider.builder("custom")
.maxConnections(500)
.maxIdleTime(Duration.ofSeconds(20))
.build();
// 配置HTTP客户端
HttpClient httpClient = HttpClient.create(provider)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.responseTimeout(Duration.ofSeconds(5))
.doOnConnected(conn ->
conn.addHandlerLast(new ReadTimeoutHandler(5))
.addHandlerLast(new WriteTimeoutHandler(5)));
// 构建WebClient实例
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.baseUrl("https://echo.apifox.com")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
// 添加请求日志记录功能
.filter(ExchangeFilterFunction.ofRequestProcessor(
clientRequest -> {
log.debug("Request: {} {}",
clientRequest.method(),
clientRequest.url());
return Mono.just(clientRequest);
}
))
// 添加响应日志记录功能
.filter(ExchangeFilterFunction.ofResponseProcessor(
clientResponse -> {
log.debug("Response status: {}",
clientResponse.statusCode());
return Mono.just(clientResponse);
}
))
.build();
}
}
3.3 retrieve()和exchange()区别
在使用 WebClient 进行 HTTP 请求时,retrieve() 和 exchange() 方法都可以用来处理响应,但它们有不同的用途和行为。以下是它们的主要区别:
retrieve()
- 用途:retrieve() 方法用于简化响应处理,特别是当你只需要响应体时。
- 自动错误处理:retrieve() 会自动处理 HTTP 错误状态码(例如 4xx 和 5xx),并抛出 WebClientResponseException 及其子类。
- 返回值:通常用于直接获取响应体,例如 bodyToMono(String.class) 或 bodyToFlux(String.class)。
- 适用场景:适用于大多数常见的请求处理场景,特别是当你不需要手动处理响应状态码时。
exchange()
- 用途:exchange() 方法提供了更底层的控制,允许你手动处理响应,包括响应状态码和响应头。
- 手动错误处理:exchange() 不会自动处理 HTTP 错误状态码,你需要手动检查响应状态码并进行相应的处理。
- 返回值:返回 ClientResponse 对象,你可以从中提取响应状态码、响应头和响应体。
- 适用场景:适用于需要手动处理响应状态码或响应头的复杂场景。
示例对比
retrieve()
public Mono<JSONObject> get(String q1) {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/get")
.queryParam("q1", q1)
.build())
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(JSONObject.class);
}
exchange()
public Mono<JSONObject> get(String q1) {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/get")
.queryParam("q1", q1)
.build())
.accept(MediaType.APPLICATION_JSON)
.exchangeToMono(response -> {
if (response.statusCode().is2xxSuccessful()) {
return response.bodyToMono(JSONObject.class);
} else {
return Mono.error(new RuntimeException("Request failed with status code: " + response.statusCode()));
}
});
}
4. 使用示例
4.1 基本请求操作
package com.coderjia.boot3webflux.service;
import com.alibaba.fastjson.JSONObject;
import jakarta.annotation.Resource;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
/**
* @author CoderJia
* @create 2024/12/3 下午 10:22
* @Description
**/
@Service
public class ApiService {
@Resource
private WebClient webClient;
// GET请求
public Mono<JSONObject> get(String q1) {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/get")
.queryParam("q1", q1)
.build())
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(JSONObject.class);
}
// POST请求
public Mono<JSONObject> post(JSONObject body) {
return webClient.post()
.uri("/post")
.bodyValue(body)
.retrieve()
.bodyToMono(JSONObject.class);
}
// PUT请求
public Mono<JSONObject> put(String q1, JSONObject JSONObject) {
return webClient.put()
.uri(uriBuilder -> uriBuilder
.path("/put")
.queryParam("q1", q1)
.build())
.bodyValue(JSONObject)
.retrieve()
.bodyToMono(JSONObject.class);
}
// DELETE请求
public Mono<JSONObject> delete(String q1) {
return webClient.delete()
.uri(uriBuilder -> uriBuilder
.path("/delete")
.queryParam("q1", q1)
.build())
.retrieve()
.bodyToMono(JSONObject.class);
}
}
效果展示
4.2 处理复杂响应
@Service
public class ApiService {
// 获取列表数据
public Flux<JSONObject> getAllUsers() {
return webClient.get()
.uri("/users")
.retrieve()
.bodyToFlux(JSONObject.class);
}
// 处理错误响应
public Mono<JSONObject> getUserWithErrorHandling(Long id) {
return webClient.get()
.uri("/users/{id}", id)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, clientResponse -> Mono.error(new RuntimeException("客户端错误")))
.onStatus(HttpStatusCode::is5xxServerError, clientResponse -> Mono.error(new RuntimeException("服务器错误")))
.bodyToMono(JSONObject.class);
}
// 使用exchange()方法获取完整响应
public Mono<ResponseEntity<JSONObject>> getUserWithFullResponse(Long id) {
return webClient.get()
.uri("/users/{id}", id)
.accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.toEntity(JSONObject.class));
}
}
4.3 高级用法
@Service
public class ApiService {
// 带请求头的请求
public Mono<JSONObject> getUserWithHeaders(Long id, String token) {
return webClient.get()
.uri("/users/{id}", id)
.header("Authorization", "Bearer " + token)
.retrieve()
.bodyToMono(JSONObject.class);
}
// 带查询参数的请求
public Flux<JSONObject> searchUsers(String name, int age) {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/users/search")
.queryParam("name", name)
.queryParam("age", age)
.build())
.retrieve()
.bodyToFlux(JSONObject.class);
}
// 文件上传
public Mono<String> uploadFile(FilePart filePart) {
return webClient.post()
.uri("/upload")
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromMultipartData("file", filePart))
.retrieve()
.bodyToMono(String.class);
}
}
5. 最佳实践
-
合理使用响应式类型
- 使用 Mono 用于单个对象
- 使用 Flux 用于集合数据
- 注意背压处理
-
错误处理
public Mono<JSONObject> getUserWithRetry(Long id) { return webClient.get() .uri("/users/{id}", id) .retrieve() .bodyToMono(JSONObject.class) .retryWhen(Retry.backoff(3, Duration.ofSeconds(1))) .timeout(Duration.ofSeconds(5)) .onErrorResume(TimeoutException.class, e -> Mono.error(new RuntimeException("请求超时"))); }
-
资源管理
- 使用连接池
- 设置适当的超时时间
- 实现优雅关闭
6. 注意事项
- WebClient 是非阻塞的,需要注意响应式编程的特性
- 合理配置连接池和超时参数
- 在生产环境中实现适当的错误处理和重试机制
- 注意内存使用,特别是处理大量数据时
7. 与RestTemplate对比
特性 | WebClient | RestTemplate |
---|---|---|
编程模型 | 响应式、非阻塞 | 同步、阻塞 |
性能 | 更好 | 一般 |
资源利用 | 更高效 | 一般 |
学习曲线 | 较陡 | 平缓 |
适用场景 | 高并发、响应式系统 | 简单应用、传统系统 |
8. 总结
WebClient 作为 Spring 推荐的新一代 HTTP 客户端,提供了强大的响应式编程能力和更好的性能。虽然相比 RestTemplate 有一定的学习曲线,但在现代微服务架构中,其带来的好处远超过学习成本。建议在新项目中优先考虑使用WebClient,特别是在需要处理高并发请求的场景下。