更多SpringBoot3内容请关注我的专栏:《SpringBoot3》
期待您的点赞??收藏评论
重学SpringBoot3-WebClient配置与使用详解
- 1. 简介
- 2. 环境准备
-
- 2.1 依赖配置
- 3. WebClient配置
-
- 3.1 基础配置
- 3.2 高级配置
- 3.3 retrieve()和exchange()区别
- 4. 使用示例
-
- 4.1 基本请求操作
- 4.2 处理复杂响应
- 4.3 高级用法
- 5. 最佳实践
- 6. 注意事项
- 7. 与RestTemplate对比
- 8. 总结
- 参考资料
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 {@Beanpublic 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 {@Beanpublic 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 {@Resourceprivate 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,特别是在需要处理高并发请求的场景下。
参考资料
- Spring WebClient官方文档
- Spring Boot官方文档
- Project Reactor文档