更多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文檔