前言:webClient是Spring webFlux模块提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具。webFlux对标SpringMVC,webClient相当于RestTemplate,同时也是Spring官方的Http请求工具。
一、springWebFlux简介
Spring web Flux是Spring5引入的一个新的响应式web框架,它与springMVC并行存在,旨在支持构建非阻塞异步通信和响应式处理的应用程序。webFlux 基于Reactive Streams规范,并使用了Project Reactor库作为其实现的基础。
以下是webFlux的一些关键特性和概念:
1.1非阻塞I/O
webflux支持非阻塞输入输出操作,这使得在高并发场景下可以更高效的利用系统自由。默认情况下,webFlux使用Netty服务器作为其非阻塞服务器,但也可以运行在其他支持servlet3.1+规范容器上
1.2响应式编程模型
webFlux使用了mono和flux这两种反应类型来表示单值和多值的异步数据序列。它允许开发者以声明的方式组合异步逻辑,并且能够处理复杂的场景。
1.3事件驱动架构
webFlux利用了事件驱动的架构来提高系统的吞吐量和相应性。当有数据准备好时才进行处理,而不是每个请求分配一个线程等待I/O操作完成。
1.4适用场景
a.高并发web应用:对于需要处理大量并发请求的应用来说,webFlux提供了一种有效的解决方案。
b.微服务架构中的异步服务:适合微服务间的异步调用,特别是在涉及I/O操作时。
c.实时数据流应用:例如Websochet通信、消息推送等实时数据处理场景。
1.5支持多种编程模型
a.注解驱动:类似与Spring MVC的注解方式,可以使用@RestController、@RequestMapping等注解定义控制器。
b.函数式编程:提供了函数式端点和路由定义的方式,允许以编程方式定义路由和处理函数。
1.6webClient
Spring WebFlux提供了一个新的webClient类,这是一个非阻塞的HTTP客户端,可用于发起HTTP请求并处理响应。
1.7兼容性和灵活性
webFlux可以与现有的Spring生态系统无缝集成,包括Spring Data、Spring Security等模块。
如果你正在考虑是否要在羡慕中采用webFlux,那么应该基于你的具体需求来决定。如果你的应用需要处理大量的并发连接,或者你希望利用响应式编程的优势来构建高效的微服务货实时应用程序,那么webFlux可能是一个合适的选择。然而,如果是一个相对简单的web应用,传统的Spring MVC可能就足够了。
二、WebClient简介
webClient是Spring webFlux模块提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具。webFlux对标springMvc,webClient相当与RestTemplate,同时也是Spring官方的http请求工具。
2.1传统阻塞I/O模型VS响应式I/O模型
a.传统阻塞IO模型RestTemplate
Spring5中引入了webClient作为非阻塞式Reactive Http客户端。Spring社区为了解决SpringMVC的阻塞模型在高并发场景下的性能瓶颈,推出了Spring webFlux,webFlux底层实现式久经考验的Netty非阻塞IO通信框架。其实webClient处理单个HTTP请求的响应时长并不比RestTemplate更快,但是它处理并发的能力更强,非阻塞的方式可以使用较少的线程以及硬件资源来处理更多的并发。
所以响应式非阻塞IO模型的核心意义在于,提高了单位时间内有限资源下的服务请求的并发处理能力,而不是缩短了单个服务请求的响应时长。
b.与RestTemplate相比,wbeClinet的优势
1)非阻塞响应式IO,单位时间内有限资源下支持更高的并发量。
2)支持使用java8 Lambda表达式函数。
3)支持同步、异步、stream流式传输。
2.2 webClient Api
webClient在Spring提供的webFlux中
<dependency>
<groupId></groupId>
<artifactId></artifactId>
</dependency>
(1)创建实例
a.create()创建实例
webClient cli = WebClient.cerate("http://localhost:8082")
(2)构建器
WebClient.builder的额外配置:
a.创建副本
WebClient client1 = WebClient.builder().filter(a).filter(b).build();
WebClient client2 = client1.mutate().filter(c).fliter(d).build();
一旦构建,webClient是不可变的。但是您可以按如下方式克隆并构建修改后的副本
a.编码器
WebClient WebClient = WebClient.builder().codecs(configurer->configurer.defaultCodecs().maxInMenorySize(2*1024*1024)).build();
(3)获取响应
retrieve()检索方法用于声明如何提取响应。
WebClient client = WebClient.create('https://example.org');
Mono<ResponseEntity<Person>> result = client.get()
.url('/persons/{id}').accept(MediaType.APPLICATION_JSON)
.retrieve()
.toEntity(Person.class);
或者只获得body
WebClient client = WebClient.create('https://example.org');
Mono<Person> result = client.get()
.uri("/persons/{id}",id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Person.class);
要获取解码对象流,请执行以下操作:
Flux<Quote> result = client.get()
.uri('/quotes').accept(MediaType.TEXT_EVENT_STREAM)
.retrieve()
.bodtToFlux(Quote.class);
bodyToFlux和bodyToMono:
a.bodyToFlux方法用于将响应结果处理为Flux对象,Flux是Reactor框架中表示包含零个、一个或多个元素的异步序列的类。着意味着响应式结果可能是一个包含多个元素的流,而不是单个值。
b.bodyToMono方法用于将响应结果处理为Mono对象,Mono是Reactor框架中表示包含零个或者一个元素的异步序列的类。着意味着响应结果是一个单个值或者没有值。
accept(mediaType...var1)
响应数据类型
acceptCharset(Charset...var1)
响应字符集
(4)RequestBody
@RequestMapping("/body")
public void test5(){
WebClient webClient = WebClient.create("http://localhost:8080/api/restful");
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("name", "张和");
formData.add("color", "blue");
Mono<Dog> dogMono = webClient.post()
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.bodyValue(formData)
.retrieve()
.bodyToMono(Dog.class);
System.out.println(dogMono.block());
}
@RequestMapping("/body1")
public void test6(){
WebClient webClient = WebClient.create("http://localhost:8080/api/json");
Mono<Dog> mono = Mono.just(new Dog("和", "33"));
Mono<Dog> dogMono = webClient.post()
.contentType(MediaType.APPLICATION_JSON)
.body(mono, Dog.class)
.retrieve()
.bodyToMono(Dog.class);
System.out.println(dogMono.block());
}
a.contentType()
设置请求参数格式
b.body()/bodyValue()
请求参数
(5)过滤器
filter()方法添加过滤器
public void test7(){
Mono<Dog> mono = Mono.just(new Dog("和", "33"));
WebClient webClient = WebClient.builder().filter(new ExchangeFilterFunction() {
@Override
public Mono<ClientResponse> filter(ClientRequest clientRequest, ExchangeFunction exchangeFunction) {
ClientRequest build = ClientRequest.from(clientRequest).body(mono, Dog.class).build();
return exchangeFunction.exchange(build);
}
}).build();
Mono<Dog> dogMono = webClient.post()
.uri("http://localhost:8080/api/json")
.contentType(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Dog.class);
System.out.println(dogMono.block());
}
(6)同步
WebClient的block()方法可以通过在末尾阻止结果以同步方式使用
@RequestMapping("/filter")
public void test7(){
Mono<Dog> mono = Mono.just(new Dog("和", "33"));
WebClient webClient = WebClient.builder().filter(new ExchangeFilterFunction() {
@Override
public Mono<ClientResponse> filter(ClientRequest clientRequest, ExchangeFunction exchangeFunction) {
ClientRequest build = ClientRequest.from(clientRequest).body(mono, Dog.class).build();
return exchangeFunction.exchange(build);
}
}).build();
Mono<Dog> dogMono = webClient.post()
.uri("http://localhost:8080/api/json")
.contentType(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Dog.class);
//非阻塞
System.out.println(dogMono.subscribe(dog -> {
System.out.println(dog);
}));
//阻塞
System.out.println(dogMono.block());
}
a.subscribe()非阻塞方式,用于异步处理数据流,非阻塞,适合构建高性能、响应式的应用程序。
b.block()阻塞方式,用于同步获取数据流的结果,会阻塞线程,适用于简单的测试或需要在同步环境中获取数据的场景。
三、不调用subscribe直接返回flux的区别
在使用响应式编程模型是,比如通过Project Reactor的Flux或Mono,直接返回一个Flux而不调用subscribe()方法和实际订阅(调用subscribe())之间有着重要的区别。理解这些差异对于构建高效的响应式应用程序至关重要。
3.1、直接返回Flux
当你直接返回一个Flux对象而不进行订阅时,实际上你只是创建了一个数据流的定义或者说时一个‘蓝图’。这个数据流尚未开始执行,也没有任何数据被生成或者消费。
优点:
a.延迟计算:因为数据流未被订阅,所以相关的计算也会被执行。这可以节省资源直到真正需要数据的时候。
b.组合性:可以在不同的层次上将多个操作组合起来,而不需要立即执行他们。
c.声明性:专注于描述要做什么而不是如何做,使得代码更加简洁、清晰。
示例:
@GetMapping(value = "/events", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Event> getEvents() {
return eventService.getEventStream();
}
在这个例子中,getEvents()方法返回一个Flux<Event>,但并没有调用subscribe()。WebFlux框架会自动处理这个Flux并为每个客户端请求订阅它。这种做法常见与Web控制器中,例如Spring WebFlux应用,返回的Flux被动的由框架负责对象进行订阅,并处理结果。
3.2subscribe订阅Flux
调用subscribe()方法意味着你主动开始执行这个数据流,开始产生数据并消费它。这是触发数据流的实际执行点。
作用:
a.启动数据流:只有当您调用subscribe()时,数据流才会开始产生数据并传递给订阅者。
b.处理结果:你可以提供回调来处理产生的数据项、错误或者完成信号。
示例:
eventService.getEventStream().subscribe(event -> {
System.out.println("Received event: " + event);
})
在这个例子中,subscribe()方法被调用以开始接收来自getEventStream()的事件,并在控制台打印出来。
3.3区别总结
a.执行时机:直接返回Flux只是定义了数据流并未执行;调用subscribe()则触发了数据流的执行。
b.应用场景:直接返回Flux常用与构建响应式的API接口,让框架层去管理订阅和数据流的执行;而在需要直接处理数据的地方(如业务逻辑层),则可能需要显示调用subscribe()来主动获取数据。
c.资源管理:由于Flux是惰性的,只有在订阅后才会开始消耗资源,因此直接返回F陆逊、可以更好的控制资源的使用。
四、exchange()和retrieve()方法的区别
exchange()是Spring WebFlux框架中WebClinet提供的一个方法,用于手动控制HTTP请求的响应处理流程。它返回一个Mono<ClinetResponse>,允许你对响应进行更仔细的操作,比如检查状态码、头信息、选择不同的响应体解析方式等。与.retrieve()不同,.exchange()不会自动处理响应体或者抛出异常,而是让你手动控制整个响应过程。
4.1基本概念
在使用WebClinet发起请求时,有两种常见方式:
1).retrieve() :自动处理成功响应(2xx),适用于直接获取响应体内容,简单易用但灵活性差。
2).exchange():返回Mono<ClinetResponse>,让你手动处理整个响应过程,包括状态码、错误处理、响应体解析等,更加灵活。
4.2exchange()的作用和优势,更精细的控制响应处理
a.可以根据响应的状态码做不同的处理。
b.支持自定义错误处理逻辑。
c.支持流式处理响应体(如text/event-stream)
d.可以读取响应头、cookie等元数据
4.3使用示例
示例1:基本使用.exchange()
WebClient webClient = WebClient.create("https://api.example.com");
webClient.get()
.uri("/users/1")
.exchange()
.flatMap(response -> {
if (response.statusCode().is2xxSuccessful()) {
return response.bodyToMono(User.class);
} else {
return Mono.error(new RuntimeException("Request failed with status code: " + response.statusCode()));
}
})
.subscribe(user -> System.out.println("User: " + user));
(1)总结流程图
GET https://api.example.com/users/1
↓
exchange() → Mono<ClientResponse>
↓
flatMap → 判断状态码
↓
bodyToMono 或 抛出错误
↓
subscribe → 异步获取 User 对象并打印
(2)解释代码
a.使用.exchange()获取完整响应
b.flatMap()处理响应:使用。faltMap()对Mono<ClinetResponse>进行转换。检查响应状态码是否是2xx成功状态:如果是成功响应,调用bodyToMono(User.class)将响应体反序列话为User对象。如果不是成功状态码,则返回一个错误信号Mono.error(...),中断流并通知订阅着发生了错误。
c.
调用 .subscribe() 表示启动整个响应式流,真正发送请求。这是一个异步非阻塞操作,当服务器返回数据后,回调函数 user -> System.out.println(...) 会被执行。如果请求失败(如返回非 2xx 状态码),会触发错误信号,可以通过 .onErrorResume() 或 .doOnError() 来捕获。
示例 2:处理流式响应(如 Server-Sent Events)
以流式处理方式接收并打印来自服务端的 Server-Sent Events(SSE) 数据。accept() 方法是 Spring WebFlux 中 WebClient 请求构建器(WebClient.RequestHeadersSpec)的一个方法,用于设置 HTTP 请求头中的 Accept 字段(这是 HTTP 协议中标准的请求头字段之一,通常用于 内容协商(Content Negotiation))。设置客户端期望从服务器接收的响应内容类型(即告诉服务器:“我接受什么样的数据格式”)。
webClient.get()
.uri("/stream")
.contentType(MediaType.APPLICATION_JSON) // 发送的是 JSON 数据
.accept(MediaType.TEXT_EVENT_STREAM) // 希望返回流式数据
.exchange()
.flatMapMany(clientResponse -> clientResponse.bodyToFlux(String.class))
.subscribe(data -> System.out.println("Received: " + data));
示例 3:读取响应头信息
webClient.get()
.uri("/info")
.exchange()
.flatMap(response -> {
String contentType = response.headers().contentType().map(MediaType::toString).orElse("unknown");
return response.bodyToMono(String.class)
.map(body -> "Content-Type: " + contentType + ", Body: " + body);
})
.subscribe(System.out::println);
4.4 注意事项
a.必须调用 subscribe() 启动流:.exchange() 返回的是 Mono<ClientResponse>,是惰性的,只有订阅后才会真正发送请求。
b.避免阻塞操作:如果你使用 .block() 来等待结果,会失去响应式编程的优势。
c.异常处理需显式处理:不像 .retrieve() 那样自动抛出异常,你需要自己判断状态码或使用 .onErrorResume() 处理错误。
4.5 与 .retrieve()
的对比
特性 | .retrieve() | .exchange() |
---|---|---|
响应处理 | 自动处理 2xx 成功响应 | 手动处理所有响应 |
错误处理 | 默认抛出异常 | 需要手动判断状态码或错误 |
获取响应头 | 不支持 | 支持 |
流式响应 | 支持但不够灵活 | 完全支持,可自由控制 |
使用难度 | 简单 | 复杂但更强大 |
4.6 适用场景
场景 | 推荐方法 |
---|---|
普通 GET 请求获取 JSON 数据 | .retrieve() |
需要判断不同状态码 | .exchange() |
处理 SSE 或流式响应 | .exchange() |
需要访问响应头信息 | .exchange() |
自定义错误逻辑(如重试、降级) | .exchange() |
4.7 总结
.exchange()
是 WebClient
提供的一种更底层、更灵活的方式来处理 HTTP 响应。它适用于需要精确控制响应处理流程的场景,尤其是在处理非标准响应、流式传输、自定义错误逻辑时非常有用。
五、Webclient使用示例
这段代码是典型的 Reactor 操作链,使用了 Flux
或类似响应式类型来处理一个叫 ChatResponse
的流式响应对象。它对每一个“块”(chunk)进行处理,并在完成时执行某些操作,同时处理异常情况。
响应式编程框架(如 Project Reactor 的 Flux
)来处理流式数据的逻辑,常用于处理异步或非阻塞操作,比如网络请求、事件流等。
//Flux<CommonResult<AiChatMessageSendRespVO>>
return streamResponse.map(chunk -> {
String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null;
newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况
contentBuffer.append(newContent);
// 响应结果
return success(new AiChatMessageSendRespVO()
.setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class))
.setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class)
.setContent(newContent))
);
}).doOnComplete(() -> {
// 忽略租户,因为 Flux 异步无法透传租户
TenantUtils.executeIgnore(() ->
chatMessageMapper.updateById(new AiChatMessageDO()
.setId(assistantMessage.getId())
.setSegmentIds(convertList(segmentList, AiKnowledgeSegmentDO::getId))
.setContent(contentBuffer.toString())));
}).doOnError(throwable -> {
log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", finalUserId, sendReqVO, throwable);
// 忽略租户,因为 Flux 异步无法透传租户
TenantUtils.executeIgnore(() ->
chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage())));
}).onErrorResume(error -> Flux.just(error(AiErrorCodeConstants.CHAT_STREAM_ERROR)));
5.1 Chatresponse.map(chunk -> { ... })
作用:chunk
是流中的一部分数据,可以理解为一次网络请求返回的一个片段。对 Chatresponse
流中的每一个 chunk
数据进行转换。将最终内容包装成一个“成功”的结果对象,可能是一个自定义的封装类(比如 ResponseEntity
或你项目中的统一返回格式)。
内部逻辑:
String newContent = chunk.getResult() != null ?
chunk.getResult().getOutput().getContent() :
null;
newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的情况
return success(newContent);
a.这里是在安全地获取 chunk 中的内容:
1)先检查 chunk.getResult() 是否不为 null;
2)如果不为 null,再继续获取 .getOutput().getContent();
3)否则返回 null,防止空指针异常。
b.使用了工具类 StrUtil(可能是 Hutool 工具库)将 null 转换为空字符串 "",确保不会出现 null 值。
5.2 .doOnComplete(() -> { })
a.
作用:当整个 Flux
流处理完成后触发这个回调。可以在这里添加一些清理资源、日志记录或通知用户任务完成的逻辑。
error -> Flux.just(error(AiErrorCodeConstants.CHAT_STREAM_ERROR))
5.3.doOnError(throwable -> { log.error(...); ... })
作用:throwable
是发生的异常对象。当流处理过程中发生错误时,会调用这个回调。打印错误信息和堆栈跟踪,方便调试和日志记录。
5.4.onErrorResume(error -> Flux.just(error(...))
当发生错误时,不是直接抛出异常结束流,而是“恢复”并返回一个新的错误响应。创建一个包含错误响应的新 Flux
,让流程继续下去而不是中断。
AiErrorCodeConstants.CHAT_STREAM_ERROR
可能是你项目中定义的错误码常量,表示“聊天流错误”。
error -> Flux.just(error(AiErrorCodeConstants.CHAT_STREAM_ERROR))
5.5 总结一下整段代码做了什么?
a.接收一个 Chatresponse
类型的流式响应;
b.对每个 chunk
进行内容提取和转换;
c.当流处理完成时,执行可选的后续操作(当前为空);
d.遇到错误时,打印错误信息并输出一个错误响应;
e.保证即使出错,也不会中断整个流,而是优雅地返回错误提示。
六、just()方法
在 WebFlux 中,just()
方法是 Reactor 框架(Spring WebFlux 的底层实现)用于创建包含固定元素的响应式流的核心方法。其主要作用是为 Flux
(多元素流)或 Mono
(单元素流)预定义静态数据序列。
6.1 核心作用
a.同步创建固定数据流
just()
通过直接指定元素值,创建一个立即发出预设元素的响应式流。例如:
Flux.just("A", "B", "C") // 创建包含3个字符串的流
Mono.just("Hello") // 创建包含单个字符串的 Mono
这些元素会在订阅时按顺序同步发出,无需等待异步操作。
b.支持可变参数
可接受多个参数(Flux.just(T...)
)或单个参数(Mono.just(T)
),覆盖单元素和多元素场景3。
c.创建冷序列(Cold Sequence)
通过 just()
创建的流属于冷序列:每次订阅都会重新发送全部元素。例如两次订阅同一 Flux.just(1,2,3)
会各自收到独立的 1→2→3
序列。
6.2 关键特性
a. 数据流未执行直到订阅
调用 just()
仅是声明数据流,不会触发元素发送。必须通过 subscribe()
订阅后才会实际发出数据:
Mono.just("Data").subscribe(System.out::println); // 输出 "Data"
b.空序列的特殊处理
若要创建空流,需使用 Mono.empty()
或 Flux.empty()
。而 just()
至少包含一个元素(如 Mono.just(null)
会发送 null
值)。
c.适用简单场景,不涉及复杂逻辑
适合已知静态数据的场景。若需动态生成元素(如循环、异步操作),应使用 create()
、generate()
或 fromSupplier()
等方法。
6.3 对比其他创建方法
方法 适用场景 示例
just() 固定元素序列 Flux.just(1, 2, 3)
empty() 空序列(仅发送完成信号) Mono.empty()
fromArray()/fromIterable() 从集合/数组创建流 Flux.fromArray(new int[]{1,2})
create() 动态生成元素(支持多次发送) 复杂异步逻辑
6.4 注意事项
a.非阻塞但可能阻塞线程:just()
本身是同步的,若元素生成涉及阻塞操作(如 Thread.sleep
),会破坏响应式非阻塞特性。
b.调试支持:结合 StepVerifier
测试工具可验证 just()
流的元素是否符合预期。
just() 是 WebFlux 中快速创建静态数据流的基础方法,适用于已知元素的简单场景。其核心价值在于声明式构造响应式序列,但需注意订阅机制和冷序列特性。对于动态数据或复杂逻辑,建议结合其他操作符(如 flatMap、generate)实现更灵活的流处理
在响应式编程库 Project Reactor(如 reactor-core)中,.just() 是一个非常常用的操作符,用于创建一个 包含一个或多个元素的、立即发射数据的响应式流(Mono 或 Flux)。
6.5 使用方式及示例
Mono<T>: Mono.just(T data) , 创建一个包含单个元素的 Mono,该 Mono 在被订阅时会立即发射这个元素并完成。
Flux<T>: Flux.just(T... data) 或 Flux.just(T first, T... rest) 创建一个按顺序发射多个元素的 Flux,每个元素都会被依次发射,并在最后完成。
(1)使用 Mono.just()
Mono<String> mono = Mono.just("Hello");
mono.subscribe(System.out::println);
输出:Hello
a.Mono.just("Hello")
表示一个包含单个值 "Hello"
的异步结果。
b.当调用 .subscribe()
时,它会立即发射这个值。
⚠️ 注意:
如果传入的是 null
,会抛出异常:NullPointerException
。
Mono.just(null); // ❌ 抛出 NullPointerException
如果需要支持 null 值,请使用 Mono.justOrEmpty()
(适用于可能为 null 的情况):
java
Mono<String> mono = Mono.justOrEmpty(null); // 不会发射任何数据,直接 onComplete
(2)使用 Flux.just()
Flux<String> flux = Flux.just("Apple", "Banana", "Cherry");
flux.subscribe(System.out::println);
输出:
Apple
Banana
Cherry
每个参数都会作为流中的一个元素被发射。支持多个参数,也可以传数组:
String[] fruits = {"Apple", "Banana", "Cherry"};
Flux<String> flux = Flux.just(fruits);
(3)和其它方法的区别
操作符 | 描述 | 示例 |
Mono.just(T) | 发射一个元素,然后完成 | Mono.just("OK") |
Mono.empty() | 立即完成,不发射任何数据 | Mono.empty() |
Mono.fromSupplier(() -> ...) | 懒加载,每次订阅时计算一次 | Mono.fromSupplier(() -> calculate()) |
Mono.defer(() -> Mono.just(...)) | 每次订阅都重新创建一个新的 Mono | Mono.defer(() -> Mono.just(new Date())) |
Flux.just(...) | 发射多个元素,按顺序发送 | Flux.just(1, 2, 3) |
Flux.fromIterable(list) | 从集合创建 Flux | Flux.fromIterable(Arrays.asList(1,2,3)) |
(4)适用场景
场景 | 说明 |
---|---|
返回静态数据 | 如返回固定的配置信息、常量值等 |
测试/模拟数据 | 快速构造测试用的 Mono 或 Flux |
组合响应式流 | 将同步数据嵌入到响应式链中,例如与 flatMap , map 等组合使用 |
链式处理起点 | 作为响应式链的起始点,例如:Mono.just(user).map(...) |
(5)完整示例
在 Spring WebFlux 中,这些 Mono
/ Flux
会被框架自动订阅并写入 HTTP 响应体中。
@GetMapping("/user")
public Mono<User> getUser() {
User user = new User("Alice");
return Mono.just(user); // 包装成响应式类型返回
}
@GetMapping("/numbers")
public Flux<Integer> getNumbers() {
return Flux.just(1, 2, 3, 4, 5); // 返回一个数字序列
}
方法 | 类型 | 特点 |
---|---|---|
Mono.just(T) | 单一值 | 被自动订阅后,立即发射一个值并完成 |
Flux.just(...) | 多值 | 被自动订阅后,立即按顺序发射多个值并完成 |
是否懒加载? | ❌ 否 | 数据是立即确定的 |
是否可为空? | ❌ 不可为 null(会抛异常) | 可使用 justOrEmpty() 替代 |
在响应式编程中,比如使用 Project Reactor 的 Mono
或 Flux
,需要订阅(subscribe)才会触发数据流的执行和发射。这种机制被称为“惰性求值”或“延迟执行”,意味着直到你调用 .subscribe()
方法之前,任何操作符链都不会真正开始执行。
(6)惰性求值
a.定义:在响应式流中,除非有订阅者订阅了这个流,否则不会执行任何操作。
b.目的:这种设计有助于优化资源使用,避免不必要的计算或网络请求等操作,直到确实需要处理这些数据为止。
使用 Mono.just()
创建一个 Mono 并订阅
Mono<String> mono = Mono.just("Hello World");
// 在此之前,"Hello World" 并没有被打印出来,因为还没有订阅
mono.subscribe(System.out::println); // 订阅后,输出 "Hello World"
在这个例子中,只有当 mono.subscribe()
被调用时,"Hello World" 才会被打印出来。如果没有调用 .subscribe()
,即使你创建了一个包含数据的 Mono
,也不会有任何输出。
使用 Flux.just()
创建一个 Flux 并订阅
Flux<String> flux = Flux.just("A", "B", "C");
// 同样地,这里不会立即打印任何内容
flux.subscribe(System.out::println); // 订阅后,依次输出 "A", "B", "C"
同样,对于 Flux
来说,也需要通过 .subscribe()
方法来启动数据流,并开始接收并处理元素。
(7)不主动订阅的情况
如果你只是构建了一个 Mono
或 Flux
而不进行订阅,那么这个流将永远不会被执行。这对于某些场景来说是有用的,例如当你想返回一个响应式类型从一个方法中(如 Spring WebFlux 控制器),让框架本身来负责订阅和处理响应式流。
(8)总结
a.必须订阅:为了触发数据的产生和流动,你需要对 Mono
或 Flux
进行订阅。
b.惰性求值:这种机制确保了只有在实际需要的时候才执行操作,从而提高了效率和性能。
c.应用场景:不仅限于简单的数据发射,还包括复杂的异步操作、网络请求等,所有这些都是基于订阅机制来驱动的
七、flatMap、generate、 create()、 fromSupplier()方法的作用
在响应式编程中(如 Project Reactor 的 reactor-core
),flatMap
、generate
、create()
和 fromSupplier()
是非常常用的几个操作符或创建方法。它们各自有不同的用途和适用场景,下面是它们的详细解释和使用方式。
7.1 flatMap()方法
将每个元素映射为一个新的 Publisher
(如 Mono
或 Flux
),然后并发地扁平化合并这些 Publisher 的结果,最终返回一个统一的 Flux<T>
或 Mono<T>
。
Flux<Integer> flux = Flux.just(1, 2, 3)
.flatMap(i -> Flux.range(i * 10, 3)); // 每个数字变成一个范围流
// 输出:10, 11, 12, 20, 21, 22, 30, 31, 32
flux.subscribe(System.out::println);
(1)flatMap
特点
a.异步非阻塞
b.并发执行多个内部流
c.常用于处理异步数据转换、数据库查询、HTTP 请求等
d.可控制并发数(通过 flatMap(..., concurrency)
)
(2)对比 map()方法
a.map()
是同步转换。
b.flatMap()
是异步映射 + 合并。
7.2 generate()方法
同步地生成数据流,适用于需要手动控制发射逻辑的场景。是 Flux
提供的一个工厂方法。
Flux<Integer> flux = Flux.generate(
() -> 0, // 初始状态
(state, sink) -> {
sink.next(state); // 发送当前状态
if (state == 10) {
sink.complete(); // 完成流
}
return state + 1; // 返回新的状态
}
);
flux.subscribe(System.out::println);
输出:
0
1
...
10
特点:
a.同步生成器
b.非常适合用于模拟序列、有限状态机、自定义算法等
c.必须显式调用 sink.next()
和 sink.complete()
/ sink.error()
7.3 create()方法
提供一个更灵活的方式创建 Flux,可以异步/同步、多线程、背压支持良好,适用于桥接非响应式代码与响应式流之间的桥梁。
Flux<String> flux = Flux.create(sink -> {
sink.next("Hello");
sink.next("World");
sink.complete();
});
flux.subscribe(System.out::println);
输出:
Hello
World
-
支持同步和异步发射
-
支持背压(可通过
onRequest
控制) -
适合封装第三方库、事件驱动系统、Socket 等原始数据源
7.4 create
与 generate()
的区别
方法 | 是否支持异步 | 是否支持背压 | 用途 |
---|---|---|---|
generate() | ❌ 同步 | ✅ 支持 | 手动控制同步数据流 |
create() | ✅ 支持 | ✅ 支持 | 更灵活,适合复杂场景 |
7.5 fromSupplier()
从一个 Supplier<T>
创建一个 Mono<T>
,延迟执行 Supplier 并发射其结果。
Mono<String> mono = Mono.fromSupplier(() -> "Hello from supplier");
mono.subscribe(System.out::println);
输出:
Hello from supplier
特点:
-
懒加载:只有订阅时才会执行 Supplier
-
不会多次执行 Supplier(除非重新订阅)
-
常用于包装同步计算任务,使其适应响应式链
对比 just()方法
:
方法 | 是否懒加载 | 是否接受 null |
---|---|---|
Mono.just(T) | ❌ 否,值立即确定 | ❌ 不允许 null |
Mono.fromSupplier(Supplier<T>) | ✅ 是,订阅时才执行 | ✅ 允许返回 null |
7.6 总结对比表
方法 | 类型 | 是否异步 | 是否懒加载 | 是否支持背压 | 用途 |
---|---|---|---|---|---|
flatMap() | 操作符 | ✅ | ✅ | ✅ | 异步映射 + 合并多个流 |
generate() | 创建方法 | ❌ | ✅ | ✅ | 同步生成有限数据流 |
create() | 创建方法 | ✅ | ✅ | ✅ | 自定义任意数据源(同步/异步) |
fromSupplier() | 创建方法 | ❌ | ✅ | ❌ | 封装同步计算任务,延迟执行 |
使用建议(常见场景)
场景 | 推荐方法 |
---|---|
调用 HTTP API、DB 查询后映射结果 | flatMap() |
生成固定范围的数据流(如 0~10) | generate() |
接入事件监听器、Socket、队列等外部源 | create() |
包装同步计算任务,使其成为 Mono | fromSupplier() |
如果你正在构建响应式应用(如 Spring WebFlux),掌握这些操作符和创建方法可以让你更好地组合异步流程、封装业务逻辑、提升程序灵活性和性能。