SpringBoot中WebClient的實踐
什么是 WebClient?
在 Spring Boot 中,WebClient 是 Spring WebFlux 提供的一個非阻塞、響應(yīng)式的 HTTP 客戶端,用于與 RESTful 服務(wù)或其他 HTTP 服務(wù)交互。相比于傳統(tǒng)的 RestTemplate,WebClient 更加現(xiàn)代化,具有異步和非阻塞的特點,適合高性能、高并發(fā)的應(yīng)用場景。
WebClient 的特點
非阻塞 I/O:適用于響應(yīng)式編程模型,能高效處理大量并發(fā)請求。
功能強大:支持同步和異步調(diào)用,處理復(fù)雜的 HTTP 請求和響應(yīng),包括流式數(shù)據(jù)。
靈活的配置:可自定義超時、請求攔截器、認(rèn)證方式等。
響應(yīng)式編程支持:返回 Mono 或 Flux,與 Spring WebFlux 的響應(yīng)式編程模型無縫集成。
引入依賴
在使用 WebClient 之前,需要確保 Spring Boot 項目已包含相關(guān)依賴。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>配置及使用 WebClient
現(xiàn)在有以下服務(wù)
- service1服務(wù):http://localhost:8081/
- service2服務(wù):http://localhost:8082/
- common服務(wù):http://localhost:8079/
創(chuàng)建 WebClientConfig 配置類,為 service1 和 service2 配置獨立的 WebClient。
package com.example.common.config;
import io.netty.channel.ChannelOption;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.netty.tcp.TcpClient;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
/**
* 配置 WebClient,支持基礎(chǔ)功能(獨立 WebClient 實例)和高級特性(超時、攔截器、內(nèi)存限制)。
*/
@Configuration
public class WebClientConfig {
/**
* 配置 WebClient,用于調(diào)用 service1(http://localhost:8081)
*
* @param builder WebClient.Builder 實例
* @return 針對 service1 的 WebClient 實例
*/
@Bean(name = "service1WebClient")
public WebClient service1WebClient(WebClient.Builder builder) {
return builder
.baseUrl("http://localhost:8081") // 配置 service1 的基本 URL
.defaultHeader("Content-Type", "application/json") // 設(shè)置默認(rèn)請求頭
.exchangeStrategies(
ExchangeStrategies.builder()
.codecs(configurer -> configurer
.defaultCodecs()
.maxInMemorySize(16 * 1024 * 1024)) // 設(shè)置最大內(nèi)存限制為 16MB
.build())
.filter(logRequest()) // 添加請求日志攔截器
.filter(logResponse()) // 添加響應(yīng)日志攔截器
.build();
}
/**
* 配置 WebClient,用于調(diào)用 service2(http://localhost:8082)
*
* @param builder WebClient.Builder 實例
* @return 針對 service2 的 WebClient 實例
*/
@Bean(name = "service2WebClient")
public WebClient service2WebClient(WebClient.Builder builder) {
return builder
.baseUrl("http://localhost:8082") // 配置 service2 的基本 URL
.defaultHeader("Content-Type", "application/json") // 設(shè)置默認(rèn)請求頭
.filter(logRequest()) // 添加請求日志攔截器
.filter(logResponse()) // 添加響應(yīng)日志攔截器
.build();
}
/**
* 提供全局的 WebClient.Builder 配置,支持超時和高級功能。
*
* @return 配置好的 WebClient.Builder
*/
@Bean
public WebClient.Builder webClientBuilder() {
// 配置 TCP 客戶端,設(shè)置連接超時、讀超時和寫超時
TcpClient tcpClient = TcpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) // 連接超時 5秒
.doOnConnected(connection ->
connection.addHandlerLast(new ReadTimeoutHandler(5)) // 讀超時 5秒
.addHandlerLast(new WriteTimeoutHandler(5))); // 寫超時 5秒
// 使用配置的 TcpClient 創(chuàng)建 HttpClient
HttpClient httpClient = HttpClient.from(tcpClient);
// 創(chuàng)建 WebClient.Builder 并配置 HttpClient 和攔截器
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient)) // 配置 HttpClient
.filter(logRequest()) // 請求日志攔截器
.filter(logResponse()); // 響應(yīng)日志攔截器
}
/**
* 請求日志攔截器:記錄請求的詳細(xì)信息(方法和 URL)
*
* @return ExchangeFilterFunction 攔截器
*/
private ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(request -> {
System.out.println("Request: " + request.method() + " " + request.url());
return Mono.just(request);
});
}
/**
* 響應(yīng)日志攔截器:記錄響應(yīng)的狀態(tài)碼
*
* @return ExchangeFilterFunction 攔截器
*/
private ExchangeFilterFunction logResponse() {
return ExchangeFilterFunction.ofResponseProcessor(response -> {
System.out.println("Response status: " + response.statusCode());
return Mono.just(response);
});
}
}
service1相應(yīng)的接口
package cloud.service1.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* Service1 的控制器類,用于處理與API相關(guān)的請求.
* 該類被Spring框架管理,作為處理HTTP請求的一部分.
*/
@RestController
@RequestMapping("/api/service1")
public class Service1Controller {
/**
* 獲取Service1的數(shù)據(jù)信息.
*
* @return 包含服務(wù)信息的映射,包括服務(wù)名稱和問候消息.
*/
@GetMapping("/data")
public Map<String, String> getData() {
// 返回一個不可變的映射,包含服務(wù)名稱和問候消息
return Map.of("service", "service1", "message", "Hello from Service1");
}
}
service2相應(yīng)的接口
package cloud.service2.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* Service2的控制器類,用于處理與Service2相關(guān)的HTTP請求.
* 該類被Spring框架管理,作為處理RESTful請求的控制器.
*/
@RestController
@RequestMapping("/api/service2")
public class Service2Controller {
/**
* 處理GET請求到/api/service2/info,返回Service2的信息.
*
* @return 包含服務(wù)信息的Map,包括服務(wù)名稱和歡迎消息.
*/
@GetMapping("/info")
public Map<String, String> getInfo() {
return Map.of("service", "service2", "message", "Hello from Service2");
}
}
服務(wù)調(diào)用實現(xiàn)
package com.example.common.service;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
/**
* CommonService 類提供了對其他服務(wù)進(jìn)行調(diào)用的方法
* 它通過 WebClient 實例與 service1 和 service2 進(jìn)行通信
*/
@Service
public class CommonService {
// 用于與 service1 通信的 WebClient 實例
private final WebClient service1WebClient;
// 用于與 service2 通信的 WebClient 實例
private final WebClient service2WebClient;
/**
* 構(gòu)造函數(shù)注入兩個 WebClient 實例
*
* @param service1WebClient 用于 service1 的 WebClient
* @param service2WebClient 用于 service2 的 WebClient
*/
public CommonService(
@Qualifier("service1WebClient") WebClient service1WebClient,
@Qualifier("service2WebClient") WebClient service2WebClient) {
this.service1WebClient = service1WebClient;
this.service2WebClient = service2WebClient;
}
/**
* 調(diào)用 service1 的接口
*
* @return 來自 service1 的數(shù)據(jù)
*/
public Mono<String> callService1() {
// 通過 service1WebClient 調(diào)用 service1 的 API,并處理可能的錯誤
return service1WebClient.get()
.uri("/api/service1/data")
.retrieve()
.bodyToMono(String.class)
.onErrorResume(e -> {
// 錯誤處理:打印錯誤信息并返回錯誤提示
System.err.println("Error calling service1: " + e.getMessage());
return Mono.just("Error calling service1");
});
}
/**
* 調(diào)用 service2 的接口
*
* @return 來自 service2 的數(shù)據(jù)
*/
public Mono<String> callService2() {
// 通過 service2WebClient 調(diào)用 service2 的 API,并處理可能的錯誤
return service2WebClient.get()
.uri("/api/service2/info")
.retrieve()
.bodyToMono(String.class)
.onErrorResume(e -> {
// 錯誤處理:打印錯誤信息并返回錯誤提示
System.err.println("Error calling service2: " + e.getMessage());
return Mono.just("Error calling service2");
});
}
}
package com.example.common.controller;
import com.example.common.service.CommonService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
/**
* 通用控制器類,處理與通用服務(wù)相關(guān)的API請求
*/
@RestController
@RequestMapping("/api/common")
public class CommonController {
// 注入通用服務(wù)接口,用于調(diào)用具體的服務(wù)方法
private final CommonService commonService;
/**
* 構(gòu)造函數(shù)注入CommonService實例
*
* @param commonService 通用服務(wù)接口實例
*/
public CommonController(CommonService commonService) {
this.commonService = commonService;
}
/**
* 調(diào)用 service1 的接口
*
* @return service1 的響應(yīng)數(shù)據(jù)
*/
@GetMapping("/service1")
public Mono<String> getService1Data() {
return commonService.callService1();
}
/**
* 調(diào)用 service2 的接口
*
* @return service2 的響應(yīng)數(shù)據(jù)
*/
@GetMapping("/service2")
public Mono<String> getService2Info() {
return commonService.callService2();
}
}
測試接口


優(yōu)化實踐
將上述代碼進(jìn)一步優(yōu)化和整合以確保代碼可維護性和高效性。
package com.example.common.config;
import io.netty.channel.ChannelOption;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.netty.tcp.TcpClient;
/**
* 配置 WebClient 的各類設(shè)置和日志記錄
*/
@Configuration
public class WebClientConfig {
/**
* 全局 WebClient.Builder 配置
*
* @return 配置好的 WebClient.Builder
*/
@Bean
public WebClient.Builder webClientBuilder() {
// 配置 TCP 客戶端的連接、讀取、寫入超時時間
TcpClient tcpClient = TcpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) // 連接超時
.doOnConnected(conn -> conn
.addHandlerLast(new ReadTimeoutHandler(5)) // 讀超時
.addHandlerLast(new WriteTimeoutHandler(5))); // 寫超時
// 將 TCP 客戶端配置應(yīng)用到 HTTP 客戶端
HttpClient httpClient = HttpClient.from(tcpClient);
// 配置 WebClient 構(gòu)建器,包括 HTTP 連接器、交換策略、請求和響應(yīng)日志
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.exchangeStrategies(ExchangeStrategies.builder()
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024)) // 內(nèi)存限制
.build())
.filter(logRequest()) // 請求日志
.filter(logResponse()); // 響應(yīng)日志
}
/**
* 針對 service1 的 WebClient 配置
*
* @param builder 全局配置的 WebClient.Builder
* @return 配置好的 WebClient 實例
*/
@Bean(name = "service1WebClient")
public WebClient service1WebClient(WebClient.Builder builder) {
// 為 service1 配置特定的 base URL 和默認(rèn)頭部
return builder
.baseUrl("http://localhost:8081")
.defaultHeader("Content-Type", "application/json")
.build();
}
/**
* 針對 service2 的 WebClient 配置
*
* @param builder 全局配置的 WebClient.Builder
* @return 配置好的 WebClient 實例
*/
@Bean(name = "service2WebClient")
public WebClient service2WebClient(WebClient.Builder builder) {
// 為 service2 配置特定的 base URL 和默認(rèn)頭部
return builder
.baseUrl("http://localhost:8082")
.defaultHeader("Content-Type", "application/json")
.build();
}
/**
* 請求日志攔截器
*
* @return 記錄請求日志的 ExchangeFilterFunction
*/
private ExchangeFilterFunction logRequest() {
// 攔截請求并打印請求方法和URL
return ExchangeFilterFunction.ofRequestProcessor(request -> {
System.out.println("Request: " + request.method() + " " + request.url());
return Mono.just(request);
});
}
/**
* 響應(yīng)日志攔截器
*
* @return 記錄響應(yīng)日志的 ExchangeFilterFunction
*/
private ExchangeFilterFunction logResponse() {
// 攔截響應(yīng)并打印響應(yīng)狀態(tài)碼
return ExchangeFilterFunction.ofResponseProcessor(response -> {
System.out.println("Response status: " + response.statusCode());
return Mono.just(response);
});
}
}
package com.example.common.service;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.util.Map;
/**
* CommonService 類提供了調(diào)用兩個不同服務(wù)的公共方法,并合并其結(jié)果
*/
@Service
public class CommonService {
// service1 的 WebClient 實例
private final WebClient service1WebClient;
// service2 的 WebClient 實例
private final WebClient service2WebClient;
/**
* 構(gòu)造函數(shù)注入 WebClient 實例
*
* @param service1WebClient service1 的 WebClient
* @param service2WebClient service2 的 WebClient
*/
public CommonService(WebClient service1WebClient, WebClient service2WebClient) {
this.service1WebClient = service1WebClient;
this.service2WebClient = service2WebClient;
}
/**
* 異步調(diào)用 service1 和 service2,并返回合并結(jié)果(JSON 格式)
*
* @return 包含兩個服務(wù)響應(yīng)的 Mono 對象
*/
public Mono<Map<String, Map<String, String>>> callServicesAsync() {
// 調(diào)用 service1,返回 Map 響應(yīng)
Mono<Map<String, String>> service1Response = service1WebClient.get()
// 設(shè)置請求的URI
.uri("/api/service1/data")
// 檢索響應(yīng)
.retrieve()
// 處理錯誤狀態(tài)
.onStatus(
// 檢查狀態(tài)是否為4xx或5xx
status -> status.is4xxClientError() || status.is5xxServerError(),
// 如果是,創(chuàng)建一個運行時異常
response -> Mono.error(new RuntimeException("Service1 Error: " + response.statusCode()))
)
// 將響應(yīng)體轉(zhuǎn)換為Mono<Map<String, String>>
.bodyToMono(new ParameterizedTypeReference<Map<String, String>>() {})
// 處理錯誤
.onErrorResume(e -> {
// 打印錯誤信息
System.err.println("Error calling service1: " + e.getMessage());
// 返回一個包含錯誤信息的Map
return Mono.just(Map.of("error", "Fallback response for service1"));
});
// 調(diào)用 service2,返回 Map 響應(yīng)
Mono<Map<String, String>> service2Response = service2WebClient.get()
// 設(shè)置請求的URI
.uri("/api/service2/info")
// 檢索響應(yīng)
.retrieve()
// 處理錯誤狀態(tài)
.onStatus(
// 檢查狀態(tài)是否為4xx或5xx
status -> status.is4xxClientError() || status.is5xxServerError(),
// 如果是,創(chuàng)建一個運行時異常
response -> Mono.error(new RuntimeException("Service2 Error: " + response.statusCode()))
)
// 將響應(yīng)體轉(zhuǎn)換為Mono<Map<String, String>>
.bodyToMono(new ParameterizedTypeReference<Map<String, String>>() {})
// 處理錯誤
.onErrorResume(e -> {
// 打印錯誤信息
System.err.println("Error calling service2: " + e.getMessage());
// 返回一個包含錯誤信息的Map
return Mono.just(Map.of("error", "Fallback response for service2"));
});
// 合并兩個響應(yīng)
return Mono.zip(service1Response, service2Response, (response1, response2) -> Map.of(
"service1", response1,
"service2", response2
))
// 處理合并過程中的錯誤
.onErrorResume(e -> {
// 打印錯誤信息
System.err.println("Error combining responses: " + e.getMessage());
// 返回一個包含錯誤信息的Map
return Mono.just(Map.of(
"error", Map.of(
"status", "error",
"message", e.getMessage() // 捕獲異常并輸出信息
)
));
});
}
}
package com.example.common.controller;
import com.example.common.service.CommonService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import java.util.Map;
@RestController
@RequestMapping("/api/common")
public class CommonController {
private final CommonService commonService;
public CommonController(CommonService commonService) {
this.commonService = commonService;
}
/**
* 提供異步調(diào)用的 REST 接口,返回 JSON 格式的數(shù)據(jù)
*/
@GetMapping("/service")
public Mono<Map<String, Map<String, String>>> getServicesData() {
System.out.println("Received request for combined service data");
return commonService.callServicesAsync()
.doOnSuccess(response -> System.out.println("Successfully retrieved data: " + response))
.doOnError(error -> System.err.println("Error occurred while fetching service data: " + error.getMessage()));
}
}
測試接口

結(jié)語
WebClient 是一個功能強大且靈活的非阻塞 HTTP 客戶端,特別適合在高并發(fā)和響應(yīng)式編程場景下使用,是替代傳統(tǒng) RestTemplate 的優(yōu)秀選擇。在實際項目中,通過合理配置(如超時、連接池)和優(yōu)化(如負(fù)載均衡、重試機制),可以顯著提高服務(wù)間通信的效率和可靠性,降低延遲和資源消耗。
同時,結(jié)合 Spring WebFlux 提供的響應(yīng)式編程支持,WebClient 能夠更好地應(yīng)對微服務(wù)架構(gòu)中復(fù)雜的通信需求,成為開發(fā)現(xiàn)代分布式系統(tǒng)的重要工具。
到此這篇關(guān)于SpringBoot中WebClient的實踐的文章就介紹到這了,更多相關(guān)SpringBoot WebClient內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringCloud?hystrix斷路器與全局解耦全面介紹
什么是服務(wù)降級?當(dāng)服務(wù)器壓力劇增的情況下,根據(jù)實際業(yè)務(wù)情況及流量,對一些服務(wù)和頁面有策略的不處理或換種簡單的方式處理,從而釋放服務(wù)器資源以保證核心交易正常運作或高效運作2022-10-10
Java基礎(chǔ)學(xué)習(xí)之構(gòu)造方法詳解
這篇文章主要為大家詳細(xì)介紹了Java基礎(chǔ)學(xué)習(xí)中構(gòu)造方法的概述及注意事項,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Java有一定幫助,需要的可以參考一下2022-08-08
MyBatis-Plus實現(xiàn)多數(shù)據(jù)源的示例代碼
這篇文章主要介紹了MyBatis-Plus實現(xiàn)多數(shù)據(jù)源的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
win11?idea?shift+F6快捷鍵失效問題解決方案
這篇文章主要介紹了win11?idea?shift+F6快捷鍵失效問題,本文給大家分享最新解決方案,需要的朋友可以參考下2023-08-08
SpringBoot日志配置SLF4J和Logback的方法實現(xiàn)
日志記錄是不可或缺的一部分,本文主要介紹了SpringBoot日志配置SLF4J和Logback的方法實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-04-04
SpringBoot項目如何把接口參數(shù)中的空白值替換為null值(推薦)
這篇文章主要介紹了SpringBoot項目如何把接口參數(shù)中的空白值替換為null值(推薦),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01

