SpringBoot中WebClient的實(shí)踐
什么是 WebClient?
在 Spring Boot 中,WebClient 是 Spring WebFlux 提供的一個(gè)非阻塞、響應(yīng)式的 HTTP 客戶端,用于與 RESTful 服務(wù)或其他 HTTP 服務(wù)交互。相比于傳統(tǒng)的 RestTemplate,WebClient 更加現(xiàn)代化,具有異步和非阻塞的特點(diǎn),適合高性能、高并發(fā)的應(yīng)用場(chǎng)景。
WebClient 的特點(diǎn)
非阻塞 I/O:適用于響應(yīng)式編程模型,能高效處理大量并發(fā)請(qǐng)求。
功能強(qiáng)大:支持同步和異步調(diào)用,處理復(fù)雜的 HTTP 請(qǐng)求和響應(yīng),包括流式數(shù)據(jù)。
靈活的配置:可自定義超時(shí)、請(qǐng)求攔截器、認(rèn)證方式等。
響應(yīng)式編程支持:返回 Mono 或 Flux,與 Spring WebFlux 的響應(yīng)式編程模型無(wú)縫集成。
引入依賴
在使用 WebClient 之前,需要確保 Spring Boot 項(xiàng)目已包含相關(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 配置獨(dú)立的 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ǔ)功能(獨(dú)立 WebClient 實(shí)例)和高級(jí)特性(超時(shí)、攔截器、內(nèi)存限制)。 */ @Configuration public class WebClientConfig { /** * 配置 WebClient,用于調(diào)用 service1(http://localhost:8081) * * @param builder WebClient.Builder 實(shí)例 * @return 針對(duì) service1 的 WebClient 實(shí)例 */ @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)請(qǐng)求頭 .exchangeStrategies( ExchangeStrategies.builder() .codecs(configurer -> configurer .defaultCodecs() .maxInMemorySize(16 * 1024 * 1024)) // 設(shè)置最大內(nèi)存限制為 16MB .build()) .filter(logRequest()) // 添加請(qǐng)求日志攔截器 .filter(logResponse()) // 添加響應(yīng)日志攔截器 .build(); } /** * 配置 WebClient,用于調(diào)用 service2(http://localhost:8082) * * @param builder WebClient.Builder 實(shí)例 * @return 針對(duì) service2 的 WebClient 實(shí)例 */ @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)請(qǐng)求頭 .filter(logRequest()) // 添加請(qǐng)求日志攔截器 .filter(logResponse()) // 添加響應(yīng)日志攔截器 .build(); } /** * 提供全局的 WebClient.Builder 配置,支持超時(shí)和高級(jí)功能。 * * @return 配置好的 WebClient.Builder */ @Bean public WebClient.Builder webClientBuilder() { // 配置 TCP 客戶端,設(shè)置連接超時(shí)、讀超時(shí)和寫超時(shí) TcpClient tcpClient = TcpClient.create() .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) // 連接超時(shí) 5秒 .doOnConnected(connection -> connection.addHandlerLast(new ReadTimeoutHandler(5)) // 讀超時(shí) 5秒 .addHandlerLast(new WriteTimeoutHandler(5))); // 寫超時(shí) 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()) // 請(qǐng)求日志攔截器 .filter(logResponse()); // 響應(yīng)日志攔截器 } /** * 請(qǐng)求日志攔截器:記錄請(qǐ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)的請(qǐng)求. * 該類被Spring框架管理,作為處理HTTP請(qǐng)求的一部分. */ @RestController @RequestMapping("/api/service1") public class Service1Controller { /** * 獲取Service1的數(shù)據(jù)信息. * * @return 包含服務(wù)信息的映射,包括服務(wù)名稱和問(wèn)候消息. */ @GetMapping("/data") public Map<String, String> getData() { // 返回一個(gè)不可變的映射,包含服務(wù)名稱和問(wèn)候消息 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請(qǐng)求. * 該類被Spring框架管理,作為處理RESTful請(qǐng)求的控制器. */ @RestController @RequestMapping("/api/service2") public class Service2Controller { /** * 處理GET請(qǐng)求到/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)用實(shí)現(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 類提供了對(duì)其他服務(wù)進(jìn)行調(diào)用的方法 * 它通過(guò) WebClient 實(shí)例與 service1 和 service2 進(jìn)行通信 */ @Service public class CommonService { // 用于與 service1 通信的 WebClient 實(shí)例 private final WebClient service1WebClient; // 用于與 service2 通信的 WebClient 實(shí)例 private final WebClient service2WebClient; /** * 構(gòu)造函數(shù)注入兩個(gè) WebClient 實(shí)例 * * @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 來(lái)自 service1 的數(shù)據(jù) */ public Mono<String> callService1() { // 通過(guò) service1WebClient 調(diào)用 service1 的 API,并處理可能的錯(cuò)誤 return service1WebClient.get() .uri("/api/service1/data") .retrieve() .bodyToMono(String.class) .onErrorResume(e -> { // 錯(cuò)誤處理:打印錯(cuò)誤信息并返回錯(cuò)誤提示 System.err.println("Error calling service1: " + e.getMessage()); return Mono.just("Error calling service1"); }); } /** * 調(diào)用 service2 的接口 * * @return 來(lái)自 service2 的數(shù)據(jù) */ public Mono<String> callService2() { // 通過(guò) service2WebClient 調(diào)用 service2 的 API,并處理可能的錯(cuò)誤 return service2WebClient.get() .uri("/api/service2/info") .retrieve() .bodyToMono(String.class) .onErrorResume(e -> { // 錯(cuò)誤處理:打印錯(cuò)誤信息并返回錯(cuò)誤提示 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請(qǐng)求 */ @RestController @RequestMapping("/api/common") public class CommonController { // 注入通用服務(wù)接口,用于調(diào)用具體的服務(wù)方法 private final CommonService commonService; /** * 構(gòu)造函數(shù)注入CommonService實(shí)例 * * @param commonService 通用服務(wù)接口實(shí)例 */ 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(); } }
測(cè)試接口
優(yōu)化實(shí)踐
將上述代碼進(jìn)一步優(yōu)化和整合以確保代碼可維護(hù)性和高效性。
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 客戶端的連接、讀取、寫入超時(shí)時(shí)間 TcpClient tcpClient = TcpClient.create() .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) // 連接超時(shí) .doOnConnected(conn -> conn .addHandlerLast(new ReadTimeoutHandler(5)) // 讀超時(shí) .addHandlerLast(new WriteTimeoutHandler(5))); // 寫超時(shí) // 將 TCP 客戶端配置應(yīng)用到 HTTP 客戶端 HttpClient httpClient = HttpClient.from(tcpClient); // 配置 WebClient 構(gòu)建器,包括 HTTP 連接器、交換策略、請(qǐng)求和響應(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()) // 請(qǐng)求日志 .filter(logResponse()); // 響應(yīng)日志 } /** * 針對(duì) service1 的 WebClient 配置 * * @param builder 全局配置的 WebClient.Builder * @return 配置好的 WebClient 實(shí)例 */ @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(); } /** * 針對(duì) service2 的 WebClient 配置 * * @param builder 全局配置的 WebClient.Builder * @return 配置好的 WebClient 實(shí)例 */ @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(); } /** * 請(qǐng)求日志攔截器 * * @return 記錄請(qǐng)求日志的 ExchangeFilterFunction */ private ExchangeFilterFunction logRequest() { // 攔截請(qǐng)求并打印請(qǐng)求方法和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)用兩個(gè)不同服務(wù)的公共方法,并合并其結(jié)果 */ @Service public class CommonService { // service1 的 WebClient 實(shí)例 private final WebClient service1WebClient; // service2 的 WebClient 實(shí)例 private final WebClient service2WebClient; /** * 構(gòu)造函數(shù)注入 WebClient 實(shí)例 * * @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 包含兩個(gè)服務(wù)響應(yīng)的 Mono 對(duì)象 */ public Mono<Map<String, Map<String, String>>> callServicesAsync() { // 調(diào)用 service1,返回 Map 響應(yīng) Mono<Map<String, String>> service1Response = service1WebClient.get() // 設(shè)置請(qǐng)求的URI .uri("/api/service1/data") // 檢索響應(yīng) .retrieve() // 處理錯(cuò)誤狀態(tài) .onStatus( // 檢查狀態(tài)是否為4xx或5xx status -> status.is4xxClientError() || status.is5xxServerError(), // 如果是,創(chuàng)建一個(gè)運(yùn)行時(shí)異常 response -> Mono.error(new RuntimeException("Service1 Error: " + response.statusCode())) ) // 將響應(yīng)體轉(zhuǎn)換為Mono<Map<String, String>> .bodyToMono(new ParameterizedTypeReference<Map<String, String>>() {}) // 處理錯(cuò)誤 .onErrorResume(e -> { // 打印錯(cuò)誤信息 System.err.println("Error calling service1: " + e.getMessage()); // 返回一個(gè)包含錯(cuò)誤信息的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è)置請(qǐng)求的URI .uri("/api/service2/info") // 檢索響應(yīng) .retrieve() // 處理錯(cuò)誤狀態(tài) .onStatus( // 檢查狀態(tài)是否為4xx或5xx status -> status.is4xxClientError() || status.is5xxServerError(), // 如果是,創(chuàng)建一個(gè)運(yùn)行時(shí)異常 response -> Mono.error(new RuntimeException("Service2 Error: " + response.statusCode())) ) // 將響應(yīng)體轉(zhuǎn)換為Mono<Map<String, String>> .bodyToMono(new ParameterizedTypeReference<Map<String, String>>() {}) // 處理錯(cuò)誤 .onErrorResume(e -> { // 打印錯(cuò)誤信息 System.err.println("Error calling service2: " + e.getMessage()); // 返回一個(gè)包含錯(cuò)誤信息的Map return Mono.just(Map.of("error", "Fallback response for service2")); }); // 合并兩個(gè)響應(yīng) return Mono.zip(service1Response, service2Response, (response1, response2) -> Map.of( "service1", response1, "service2", response2 )) // 處理合并過(guò)程中的錯(cuò)誤 .onErrorResume(e -> { // 打印錯(cuò)誤信息 System.err.println("Error combining responses: " + e.getMessage()); // 返回一個(gè)包含錯(cuò)誤信息的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())); } }
測(cè)試接口
結(jié)語(yǔ)
WebClient 是一個(gè)功能強(qiáng)大且靈活的非阻塞 HTTP 客戶端,特別適合在高并發(fā)和響應(yīng)式編程場(chǎng)景下使用,是替代傳統(tǒng) RestTemplate 的優(yōu)秀選擇。在實(shí)際項(xiàng)目中,通過(guò)合理配置(如超時(shí)、連接池)和優(yōu)化(如負(fù)載均衡、重試機(jī)制),可以顯著提高服務(wù)間通信的效率和可靠性,降低延遲和資源消耗。
同時(shí),結(jié)合 Spring WebFlux 提供的響應(yīng)式編程支持,WebClient 能夠更好地應(yīng)對(duì)微服務(wù)架構(gòu)中復(fù)雜的通信需求,成為開(kāi)發(fā)現(xiàn)代分布式系統(tǒng)的重要工具。
到此這篇關(guān)于SpringBoot中WebClient的實(shí)踐的文章就介紹到這了,更多相關(guān)SpringBoot WebClient內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringCloud?hystrix斷路器與全局解耦全面介紹
什么是服務(wù)降級(jí)?當(dāng)服務(wù)器壓力劇增的情況下,根據(jù)實(shí)際業(yè)務(wù)情況及流量,對(duì)一些服務(wù)和頁(yè)面有策略的不處理或換種簡(jiǎn)單的方式處理,從而釋放服務(wù)器資源以保證核心交易正常運(yùn)作或高效運(yùn)作2022-10-10Java基礎(chǔ)學(xué)習(xí)之構(gòu)造方法詳解
這篇文章主要為大家詳細(xì)介紹了Java基礎(chǔ)學(xué)習(xí)中構(gòu)造方法的概述及注意事項(xiàng),文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Java有一定幫助,需要的可以參考一下2022-08-08MyBatis-Plus實(shí)現(xiàn)多數(shù)據(jù)源的示例代碼
這篇文章主要介紹了MyBatis-Plus實(shí)現(xiàn)多數(shù)據(jù)源的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11java中TCP實(shí)現(xiàn)回顯服務(wù)器及客戶端
本文主要介紹了java中TCP實(shí)現(xiàn)回顯服務(wù)器及客戶端,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02java實(shí)現(xiàn)雷霆戰(zhàn)機(jī)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)雷霆戰(zhàn)機(jī),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06win11?idea?shift+F6快捷鍵失效問(wèn)題解決方案
這篇文章主要介紹了win11?idea?shift+F6快捷鍵失效問(wèn)題,本文給大家分享最新解決方案,需要的朋友可以參考下2023-08-08SpringBoot日志配置SLF4J和Logback的方法實(shí)現(xiàn)
日志記錄是不可或缺的一部分,本文主要介紹了SpringBoot日志配置SLF4J和Logback的方法實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-04-04SpringBoot項(xiàng)目如何把接口參數(shù)中的空白值替換為null值(推薦)
這篇文章主要介紹了SpringBoot項(xiàng)目如何把接口參數(shù)中的空白值替換為null值(推薦),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01