java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot WebClient

SpringBoot WebClient 全面解析

作者:西凉的悲伤

WebClient是Spring5非阻塞响应式HTTP客户端,适用于高并发场景和微服务通信,替代RestTemplate更推荐新项目,支持异步并发请求和链式API风格,这篇文章给大家介绍SpringBoot WebClient全面解析,感兴趣的朋友一起看看吧

一、什么是 WebClient?

WebClient 是 Spring 5 引入的一个现代 HTTP 客户端,属于 Spring WebFlux 模块,用来发送 HTTP 请求(GET、POST、PUT、DELETE 等)。
它可以替代传统的 RestTemplate。(Home)

WebClient 它底层基于:

因此:

少量线程
处理大量请求

这也是它高并发能力强的原因。(Home)

官方文档:

二、 WebClient 能解决什么问题?

它主要用于:

例如:

你的系统
   ↓
WebClient
   ↓
支付宝接口 / 微信接口 / 第三方系统

三、WebClient 和 RestTemplate 的区别

对比项WebClientRestTemplate
所属Spring WebFluxSpring MVC
是否异步支持默认同步阻塞
是否非阻塞
是否支持响应式支持 Mono / Flux不支持
并发能力一般
是否支持流式处理支持一般
推荐程度新项目推荐维护模式
API 风格链式 fluent API模板式 API

Spring 官方已经说明:

四、WebClient 的核心优势

1. 非阻塞(Non-Blocking)

传统 RestTemplate:

线程发请求
↓
一直等待响应
↓
线程被占用

WebClient:

线程发请求
↓
不用等待
↓
线程去处理别的任务
↓
响应回来再通知

因此:

支持同步调用

WebClient 虽然是响应式的,但你也能:

.block()

变成同步调用。

因此:

即使你不是响应式项目,也能使用 WebClient。

2. 支持异步

可以同时请求多个接口:

Mono<User> userMono = webClient.get()...
Mono<Order> orderMono = webClient.get()...

最后组合:

Mono.zip(userMono, orderMono)

3. 链式 API 更现代

传统的 RestTemplate:

restTemplate.exchange(...)

而 WebClient:

webClient.get()
         .uri("/user")
         .retrieve()
         .bodyToMono(User.class);

更像:

五、WebClient 的核心对象

最重要的:

WebClient

它类似:

浏览器客户端

负责:

六、Mono 和 Flux 是什么?

WebClient 基于 Reactor。

两个核心类:

类型含义
Mono0~1 个结果
Flux0~N 个结果

例如:

Mono<User>
Flux<User>

表示:

Mono<User>表示未来会返回一个 User
Flux<User>表示未来会返回多个 User

七、如何引入 WebClient?

Maven

Spring Boot 项目:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

即使你项目不是 WebFlux 项目,也能单独使用 WebClient。

八、WebClient 的基本创建方式

1. 创建 WebClient 最简单的使用方式

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() {
        //配置超时和日志
        HttpClient httpClient = HttpClient.create()
            .responseTimeout(Duration.ofSeconds(10))
            .wiretap(true);
        return WebClient.builder()
                //基本url域名
                .baseUrl("https://api.example.com")
            	//默认请求头增加类型为json
                .defaultHeader(HttpHeaders.CONTENT_TYPE,MediaType.APPLICATION_JSON_VALUE)
                //默认请求头增加token
        		.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer abcdefg")
            	//超时配置和日志
            	.clientConnector(new ReactorClientHttpConnector(httpClient))
                .build();
    }
}

通常 token 是动态获取的,可以使用 filter,下面的示例使用 getToken() 动态获取 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 配置项包括:

注入使用

@Autowired
private WebClient webClient;

九、GET 带参数

请求

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();

最终请求:

https://api.example.com/user?page=1&size=10

get()

表示 GET 请求:

webClient.get()

uri()

请求地址。

如果请求地址很简单可以这样写:

.uri("/user")

headers()

增加请求头,可以在配置类里配置默认的请求头

retrieve()

开始发送请求并获取响应:

.retrieve()

bodyToMono()

响应转对象:

.bodyToMono(String.class)

block()

阻塞等待结果:

.block();

注意:

WebClient 本身是异步的。

调用:

.block()

才会变成同步等待。

十、GET 返回对象

User 类

@Data
public class User {
    private Long id;
    private String name;
}

调用

User user = webClient.get()
        .uri("/user")
        .retrieve()
        .bodyToMono(User.class)
        .block();

如果带参数和请求头:

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 会自动 JSON 转 User 对象。

十一、POST 请求示例

请求

POST /user
Content-Type: application/json

请求体:

{
  "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 请求
    	.contentType(MediaType.APPLICATION_JSON)
   		 //单次请求携带 Token. 可在配置类全局配置 Token
    	.header(HttpHeaders.AUTHORIZATION, token)
        .bodyValue(req)
        .retrieve()
        .bodyToMono(String.class)
        .block();

如果是表单请求:

.contentType(MediaType.APPLICATION_FORM_URLENCODED)

bodyValue() 是什么?

它表示:

把对象转为请求体 JSON

等价于:

{
  "name":"张三"
}

十二、PUT 请求

webClient.put()
        .uri("/user/1")
        .bodyValue(req)
        .retrieve()
        .bodyToMono(String.class)
        .block();

十三、DELETE 请求

webClient.delete()
        .uri("/user/1")
        .retrieve()
        .bodyToMono(String.class)
        .block();

十四、下载文件

这是企业里非常常见的场景。

下载文件为 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(内存溢出)

流式下载

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();

十五、错误处理

普通写法

webClient.get()
        .uri("/user")
        .retrieve()
        .bodyToMono(String.class)

如果:

404
500

会抛异常。

onStatus 状态码错误处理

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() 区别

retrieve()(最常用)

适合:

简单场景:

.retrieve()
.bodyToMono(...)

exchangeToMono()

适合高级场景:

String result = webClient.get()
        .uri("/user")
        .exchangeToMono(response -> {
            if (response.statusCode().is2xxSuccessful()) {
                return response.bodyToMono(String.class);
            }
            return Mono.error(
                    new RuntimeException("请求失败")
            );
        })
        .block();

十七、 WebClient适合场景与不适合场景?

非常适合:

不适合的场景:

如果你的项目:

完全同步
低并发
传统 MVC

那么:

RestTemplate / RestClient

可能更简单。

社区里也有很多开发者提到:

十八、WebClient 学习路线

建议按这个顺序学习:

  1. WebClient 基础 API
  2. Mono / Flux
  3. Reactor
  4. 异步编程
  5. 响应式编程
  6. Netty
  7. 背压(BackPressure)

十九、最常用写法总结

1.GET

webClient.get()

2.POST

webClient.post()

3.设置 header

.header()

4.设置 body

.bodyValue()

5.获取响应

.retrieve()
.bodyToMono()

6.阻塞等待

.block()

二十、完整实战示例

封装 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请求
    public String getWithToken(String token) {
        return webClient.get()
                .uri("/user/info")
                .header(HttpHeaders.AUTHORIZATION,
                        "Bearer " + token)
                .retrieve()
                .bodyToMono(String.class)
                .block();
    }
}

二十一、最后总结

WebClient 本质上:

Spring 官方现代 HTTP 客户端

它最大的特点:

企业中现在越来越多:

微服务 + WebClient

的组合。

但它的核心难点其实不是 WebClient 本身,而是:

Mono / Flux / Reactor

到此这篇关于SpringBoot WebClient 全面解析的文章就介绍到这了,更多相关SpringBoot WebClient 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文