SpringBoot WebClient 全面解析
一、什么是 WebClient?
WebClient 是 Spring 5 引入的一個(gè)現(xiàn)代 HTTP 客戶端,屬于 Spring WebFlux 模塊,用來發(fā)送 HTTP 請(qǐng)求(GET、POST、PUT、DELETE 等)。
它可以替代傳統(tǒng)的 RestTemplate。(Home)
WebClient 它底層基于:
- Reactor
- Netty
- NIO
因此:
少量線程 處理大量請(qǐng)求
這也是它高并發(fā)能力強(qiáng)的原因。(Home)
官方文檔:
二、 WebClient 能解決什么問題?
它主要用于:
- 調(diào)用第三方接口
- 微服務(wù)之間通信
- 下載文件
- 上傳文件
- 調(diào)用 AI / OpenAPI 接口
- 高并發(fā) HTTP 請(qǐng)求
- 異步并發(fā)調(diào)用多個(gè)接口
例如:
你的系統(tǒng) ↓ WebClient ↓ 支付寶接口 / 微信接口 / 第三方系統(tǒng)
三、WebClient 和 RestTemplate 的區(qū)別
| 對(duì)比項(xiàng) | WebClient | RestTemplate |
|---|---|---|
| 所屬 | Spring WebFlux | Spring MVC |
| 是否異步 | 支持 | 默認(rèn)同步阻塞 |
| 是否非阻塞 | 是 | 否 |
| 是否支持響應(yīng)式 | 支持 Mono / Flux | 不支持 |
| 并發(fā)能力 | 高 | 一般 |
| 是否支持流式處理 | 支持 | 一般 |
| 推薦程度 | 新項(xiàng)目推薦 | 維護(hù)模式 |
| API 風(fēng)格 | 鏈?zhǔn)?fluent API | 模板式 API |
Spring 官方已經(jīng)說明:
RestTemplate進(jìn)入 maintenance mode(維護(hù)模式)- 新項(xiàng)目更推薦
WebClient(Reddit)
四、WebClient 的核心優(yōu)勢(shì)
1. 非阻塞(Non-Blocking)
傳統(tǒng) RestTemplate:
線程發(fā)請(qǐng)求 ↓ 一直等待響應(yīng) ↓ 線程被占用
WebClient:
線程發(fā)請(qǐng)求 ↓ 不用等待 ↓ 線程去處理別的任務(wù) ↓ 響應(yīng)回來再通知
因此:
- 更省線程
- 更適合高并發(fā)
- 更適合微服務(wù)
支持同步調(diào)用
WebClient 雖然是響應(yīng)式的,但你也能:
.block()
變成同步調(diào)用。
因此:
即使你不是響應(yīng)式項(xiàng)目,也能使用 WebClient。
2. 支持異步
可以同時(shí)請(qǐng)求多個(gè)接口:
Mono<User> userMono = webClient.get()... Mono<Order> orderMono = webClient.get()...
最后組合:
Mono.zip(userMono, orderMono)
3. 鏈?zhǔn)?API 更現(xiàn)代
傳統(tǒng)的 RestTemplate:
restTemplate.exchange(...)
而 WebClient:
webClient.get()
.uri("/user")
.retrieve()
.bodyToMono(User.class);
更像:
- Java8 Stream
- 函數(shù)式編程
- Reactor 響應(yīng)式風(fēng)格
五、WebClient 的核心對(duì)象
最重要的:
WebClient
它類似:
瀏覽器客戶端
負(fù)責(zé):
- 發(fā)請(qǐng)求
- 設(shè)置 header
- 設(shè)置 token
- 接收響應(yīng)
- 下載文件
六、Mono 和 Flux 是什么?
WebClient 基于 Reactor。
兩個(gè)核心類:
| 類型 | 含義 |
|---|---|
| Mono | 0~1 個(gè)結(jié)果 |
| Flux | 0~N 個(gè)結(jié)果 |
例如:
Mono<User> Flux<User>
表示:
Mono<User>表示未來會(huì)返回一個(gè) User Flux<User>表示未來會(huì)返回多個(gè) User
七、如何引入 WebClient?
Maven
Spring Boot 項(xiàng)目:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>即使你項(xiàng)目不是 WebFlux 項(xiàng)目,也能單獨(dú)使用 WebClient。
八、WebClient 的基本創(chuàng)建方式
1. 創(chuàng)建 WebClient 最簡(jiǎn)單的使用方式
WebClient webClient = WebClient.create();
或者:
WebClient webClient = WebClient.create("https://api.example.com");
或者:
WebClient webClient = WebClient.builder()
.baseUrl("https://api.example.com")
.build();
2.Spring Bean 配置類方式(推薦)
配置類
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient() {
//配置超時(shí)和日志
HttpClient httpClient = HttpClient.create()
.responseTimeout(Duration.ofSeconds(10))
.wiretap(true);
return WebClient.builder()
//基本url域名
.baseUrl("https://api.example.com")
//默認(rèn)請(qǐng)求頭增加類型為json
.defaultHeader(HttpHeaders.CONTENT_TYPE,MediaType.APPLICATION_JSON_VALUE)
//默認(rèn)請(qǐng)求頭增加token
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer abcdefg")
//超時(shí)配置和日志
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
}通常 token 是動(dòng)態(tài)獲取的,可以使用 filter,下面的示例使用 getToken() 動(dòng)態(tài)獲取 token。
Filter 示例
@Bean
public WebClient webClient() {
return WebClient.builder()
.filter((request, next) -> {
ClientRequest newRequest =
ClientRequest.from(request)
.header(HttpHeaders.AUTHORIZATION,"Bearer " + getToken())
.build();
return next.exchange(newRequest);})
.build();
}官方 builder 配置項(xiàng)包括:
- baseUrl
- defaultHeader
- filter
- codec
- timeout
- connector 等 (Spring 框架)
注入使用
@Autowired private WebClient webClient;
九、GET 帶參數(shù)
請(qǐng)求
GET /user?page=1&size=10
代碼
String result = webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/user")
.queryParam("page", 1)
.queryParam("size", 10)
.build())
.headers(headers -> {
headers.setBearerAuth(token);
headers.add("appId", "1001");})
.retrieve()
.bodyToMono(String.class)
.block();
最終請(qǐng)求:
https://api.example.com/user?page=1&size=10
get()
表示 GET 請(qǐng)求:
webClient.get()
uri()
請(qǐng)求地址。
如果請(qǐng)求地址很簡(jiǎn)單可以這樣寫:
.uri("/user")
headers()
增加請(qǐng)求頭,可以在配置類里配置默認(rèn)的請(qǐng)求頭
retrieve()
開始發(fā)送請(qǐng)求并獲取響應(yīng):
.retrieve()
bodyToMono()
響應(yīng)轉(zhuǎn)對(duì)象:
.bodyToMono(String.class)
block()
阻塞等待結(jié)果:
.block();
注意:
WebClient 本身是異步的。
調(diào)用:
.block()
才會(huì)變成同步等待。
十、GET 返回對(duì)象
User 類
@Data
public class User {
private Long id;
private String name;
}調(diào)用
User user = webClient.get()
.uri("/user")
.retrieve()
.bodyToMono(User.class)
.block();
如果帶參數(shù)和請(qǐng)求頭:
User user = webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/user")
.queryParam("page", 1)
.queryParam("size", 10)
.build())
.headers(headers -> {
headers.setBearerAuth(token);
headers.add("appId", "1001");})
.retrieve()
.bodyToMono(User.class)
.block();通過 bodyToMono(User.class) Spring 會(huì)自動(dòng) JSON 轉(zhuǎn) User 對(duì)象。
十一、POST 請(qǐng)求示例
請(qǐng)求
POST /user Content-Type: application/json
請(qǐng)求體:
{
"name":"張三",
"password":"123",
}DTO
@Data
public class UserReq {
private String name;
private String password;
}
POST 代碼
UserReq req = new UserReq();
req.setName("張三");
req.setPassword("123");
String token = "Bearer xxxxxx";
String result = webClient.post()
.uri("/user")
//JSON 請(qǐng)求
.contentType(MediaType.APPLICATION_JSON)
//單次請(qǐng)求攜帶 Token. 可在配置類全局配置 Token
.header(HttpHeaders.AUTHORIZATION, token)
.bodyValue(req)
.retrieve()
.bodyToMono(String.class)
.block();如果是表單請(qǐng)求:
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
bodyValue() 是什么?
它表示:
把對(duì)象轉(zhuǎn)為請(qǐng)求體 JSON
等價(jià)于:
{
"name":"張三"
}
十二、PUT 請(qǐng)求
webClient.put()
.uri("/user/1")
.bodyValue(req)
.retrieve()
.bodyToMono(String.class)
.block();
十三、DELETE 請(qǐng)求
webClient.delete()
.uri("/user/1")
.retrieve()
.bodyToMono(String.class)
.block();
十四、下載文件
這是企業(yè)里非常常見的場(chǎng)景。
下載文件為 byte[]
byte[] data = webClient.get()
.uri("https://example.com/test.pdf")
//如果需要攜帶token
.header(HttpHeaders.AUTHORIZATION,"Bearer xxxxxx")
.retrieve()
.bodyToMono(byte[].class)
.block();
保存本地文件
byte[] data = webClient.get()
.uri("https://example.com/test.pdf")
//如果需要攜帶token
.header(HttpHeaders.AUTHORIZATION,"Bearer xxxxxx")
.retrieve()
.bodyToMono(byte[].class)
.block();
Files.write(
Paths.get("D:/test.pdf"),
data
);
大文件下載(推薦流式)
如果文件很大, 有幾百 MB 或者 幾 GB,不推薦上面的 byte[] 下載,否則可能 OOM(內(nèi)存溢出)
流式下載
Flux<DataBuffer> flux = webClient.get()
.uri("/download/file")
.retrieve()
.bodyToFlux(DataBuffer.class);
DataBufferUtils.write(
flux,
Paths.get("D:/big.zip"),
StandardOpenOption.CREATE
).block();或者:
webClient.get()
.uri("https://example.com/big.zip")
.retrieve()
.bodyToFlux(DataBuffer.class)
.map(DataBuffer::asByteBuffer)
.doOnNext(buffer -> {
// 寫入文件
})
.blockLast();
十五、錯(cuò)誤處理
普通寫法
webClient.get()
.uri("/user")
.retrieve()
.bodyToMono(String.class)
如果:
404
500
會(huì)拋異常。
onStatus 狀態(tài)碼錯(cuò)誤處理
String result = webClient.get()
.uri("/user")
.retrieve()
.onStatus(
HttpStatusCode::is4xxClientError,
response -> Mono.error(new RuntimeException("4xx異常"))
)
.onStatus(
HttpStatusCode::is5xxServerError,
response -> Mono.error(new RuntimeException("5xx異常"))
)
.bodyToMono(String.class)
.block();try-catch
try {
String result = webClient.get()
.uri("/test")
.retrieve()
.bodyToMono(String.class)
.block();
} catch (Exception e) {
e.printStackTrace();
}
十六、retrieve() 和 exchangeToMono() 區(qū)別
retrieve()(最常用)
適合:
- 普通接口調(diào)用
- 簡(jiǎn)潔開發(fā)
簡(jiǎn)單場(chǎng)景:
.retrieve() .bodyToMono(...)
exchangeToMono()
適合高級(jí)場(chǎng)景:
- 獲取狀態(tài)碼
- 獲取響應(yīng)頭、cookie
- 自定義響應(yīng)處理
String result = webClient.get()
.uri("/user")
.exchangeToMono(response -> {
if (response.statusCode().is2xxSuccessful()) {
return response.bodyToMono(String.class);
}
return Mono.error(
new RuntimeException("請(qǐng)求失敗")
);
})
.block();十七、 WebClient適合場(chǎng)景與不適合場(chǎng)景?
非常適合:
- 微服務(wù)
- 高并發(fā)
- API 網(wǎng)關(guān)
- 聚合接口
- AI 調(diào)用
- 并發(fā)請(qǐng)求多個(gè)服務(wù)
- SSE/流式響應(yīng)
不適合的場(chǎng)景:
如果你的項(xiàng)目:
完全同步
低并發(fā)
傳統(tǒng) MVC
那么:
RestTemplate / RestClient
可能更簡(jiǎn)單。
社區(qū)里也有很多開發(fā)者提到:
- WebFlux 會(huì)增加復(fù)雜度
- Mono / Flux 學(xué)習(xí)成本較高 (Reddit)
十八、WebClient 學(xué)習(xí)路線
建議按這個(gè)順序?qū)W習(xí):
- WebClient 基礎(chǔ) API
- Mono / Flux
- Reactor
- 異步編程
- 響應(yīng)式編程
- Netty
- 背壓(BackPressure)
十九、最常用寫法總結(jié)
1.GET
webClient.get()
2.POST
webClient.post()
3.設(shè)置 header
.header()
4.設(shè)置 body
.bodyValue()
5.獲取響應(yīng)
.retrieve() .bodyToMono()
6.阻塞等待
.block()
二十、完整實(shí)戰(zhàn)示例
封裝 HttpClientService
@Service
public class HttpClientService {
private final WebClient webClient;
public HttpClientService(WebClient.Builder builder) {
this.webClient = builder
.baseUrl("https://api.example.com")
.defaultHeader(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_JSON_VALUE)
.build();
}
// GET
public String getUser() {
return webClient.get()
.uri("/user/1")
.retrieve()
.bodyToMono(String.class)
.block();
}
// POST
public String login(LoginRequest request) {
return webClient.post()
.uri("/login")
.bodyValue(request)
.retrieve()
.bodyToMono(String.class)
.block();
}
// token請(qǐng)求
public String getWithToken(String token) {
return webClient.get()
.uri("/user/info")
.header(HttpHeaders.AUTHORIZATION,
"Bearer " + token)
.retrieve()
.bodyToMono(String.class)
.block();
}
}二十一、最后總結(jié)
WebClient 本質(zhì)上:
Spring 官方現(xiàn)代 HTTP 客戶端
它最大的特點(diǎn):
- 非阻塞
- 響應(yīng)式
- 高并發(fā)
- 異步
- 鏈?zhǔn)?API
企業(yè)中現(xiàn)在越來越多:
微服務(wù) + WebClient
的組合。
但它的核心難點(diǎn)其實(shí)不是 WebClient 本身,而是:
Mono / Flux / Reactor
到此這篇關(guān)于SpringBoot WebClient 全面解析的文章就介紹到這了,更多相關(guān)SpringBoot WebClient 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于java.lang.IncompatibleClassChangeError錯(cuò)誤解決方案
最近開發(fā)中遇到類沖突報(bào)錯(cuò) java.lang.IncompatibleClassChangeError,所以下面這篇文章主要給大家介紹了關(guān)于java.lang.IncompatibleClassChangeError錯(cuò)誤的解決方案,需要的朋友可以參考下2024-02-02
Java數(shù)據(jù)結(jié)構(gòu)之二叉排序樹的實(shí)現(xiàn)
二叉排序樹(Binary Sort Tree),又稱二叉查找樹(Binary Search Tree),亦稱二叉搜索樹。本文詳細(xì)介紹了二叉排序樹的原理,并且提供了Java代碼的完全實(shí)現(xiàn)。需要的可以參考一下2022-01-01
Spring Boot Jar 包部署腳本的實(shí)例講解
在本篇文章里小編給大家整理的是一篇關(guān)于Spring Boot Jar 包部署腳本的實(shí)例講解內(nèi)容,對(duì)此有興趣的朋友們可以跟著學(xué)習(xí)下。2021-12-12
關(guān)于SpringBoot微服務(wù)發(fā)布與部署的三種方式
SpringBoot 框架只提供了一套基于可執(zhí)行 jar 包(executable jar)格式的標(biāo)準(zhǔn)發(fā)布形式,但并沒有對(duì)部署做過多的界定,而且為了簡(jiǎn)化可執(zhí)行 jar 包的生成,SpringBoot 提供了相應(yīng)的 Maven 項(xiàng)目插件,需要的朋友可以參考下2023-05-05
Spring Validation中9個(gè)數(shù)據(jù)校驗(yàn)工具使用指南
Spring Validation作為Spring生態(tài)系統(tǒng)的重要組成部分,提供了一套強(qiáng)大而靈活的數(shù)據(jù)校驗(yàn)機(jī)制,本文給大家介紹了Spring Validation中9個(gè)數(shù)據(jù)校驗(yàn)工具的使用指南,需要的朋友可以參考下2025-05-05
Mybatis詳解動(dòng)態(tài)SQL以及單表多表查詢的應(yīng)用
MyBatis的動(dòng)態(tài)SQL是基于OGNL表達(dá)式的,它可以幫助我們方便的在SQL語句中實(shí)現(xiàn)某些邏輯,下面這篇文章主要給大家介紹了關(guān)于Mybatis超級(jí)強(qiáng)大的動(dòng)態(tài)SQL語句的相關(guān)資料,需要的朋友可以參考下2022-06-06
解決spring-boot 打成jar包后 啟動(dòng)時(shí)指定參數(shù)無效的問題
這篇文章主要介紹了解決spring-boot 打成jar包后 啟動(dòng)時(shí)指定參數(shù)無效的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
使用SpringBoot?+?Vue?+?Redis實(shí)現(xiàn)驗(yàn)證碼登錄功能全過程
在現(xiàn)代web應(yīng)用中,用戶驗(yàn)證是非常重要的一部分,這篇文章主要介紹了使用SpringBoot?+?Vue?+?Redis實(shí)現(xiàn)驗(yàn)證碼登錄功能的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-10-10

