1. Spring WebFlux响应式编程核心操作符解析
在响应式编程的世界里,数据流就像是一条奔腾不息的河流,而操作符就是控制这条河流流向和形态的各种水闸与管道。Spring WebFlux作为响应式编程在Spring生态中的实现,提供了一系列强大的操作符来处理异步数据流。今天我们就来深入探讨其中最常用的组合和延迟操作符,这些工具能让你像交响乐指挥家一样优雅地编排数据流。
提示:本文所有示例基于Reactor 3.4.x版本,需要基本的Java 8和响应式编程知识。建议在阅读时边看边动手实践,效果更佳。
1.1 环境准备与工具类
在开始前,我们先准备一个实用的日志工具类,它可以帮助我们直观地观察数据流的处理过程:
java复制import java.time.Duration;
import java.util.List;
import java.util.Optional;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
public class ReactorLogUtil {
private static Duration timeout = Duration.ofSeconds(60);
private static final String separator = "-".repeat(20);
// 打印Mono日志
public static <T> void log(String varName, Mono<T> mono) {
Object res = null;
try {
res = mono.block(timeout);
} catch (Exception ex) {
String msg = Optional.ofNullable(ex.getMessage())
.map(String::toLowerCase)
.orElse("");
if(msg.contains("timeout")) {
res = "<Timeout>";
} else {
res = "<Throw "+ex.toString()+">";
}
}
System.out.println("<Mono> " + varName + " : " +
Optional.ofNullable(res).orElse("<Empty Mono>"));
}
// 打印Flux日志
public static <T> void log(String varName, Flux<T> flux) {
String res = null;
try {
List<T> list = flux.collectList().block(timeout);
res = list.toString();
} catch (Exception ex) {
if(ex.getMessage().toLowerCase().contains("timeout")) {
res = "<Timeout>";
} else {
res = "<Throw "+ex.toString()+">";
}
}
System.out.println("<Flux> " + varName + " : " + res);
}
// 打印分隔线
public static void separator(String title) {
System.out.println("\n"+separator+" "+title+" "+separator);
}
}
这个工具类的核心原理是通过block()方法阻塞获取结果,这在生产环境中是不推荐的,但在学习和调试时非常有用。它能够:
- 捕获并格式化超时和异常情况
- 统一Mono和Flux的输出格式
- 提供清晰的分隔标记
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 组合操作符深度解析
组合操作符就像是数据流的乐高积木,让你能把多个数据流以各种方式拼接组合。下面我们来看几种最常用的组合方式。
2.1 合并操作:merge与concat
2.1.1 merge/mergeWith:无序合并
merge和mergeWith操作符用于将多个流合并为一个,特点是"先到先得"——哪个流的元素先到达就先用哪个。
java复制public static void demoMerge() {
ReactorLogUtil.separator("merge()/mergeWith()");
// 创建两个有延迟的Mono
Mono<String> name = Mono.just("Tony").delayElement(Duration.ofMillis(400));
Mono<String> age = Mono.just("69").delayElement(Duration.ofMillis(100));
// 创建两个有延迟的Flux
Flux<String> letters = Flux.just("A","B","C").delayElements(Duration.ofMillis(120));
Flux<String> numbers = Flux.just("1", "2", "3").delayElements(Duration.ofMillis(100));
// Mono合并
Flux<String> mergedMono = name.mergeWith(age);
ReactorLogUtil.log("mergedMono", mergedMono);
// Flux合并的两种写法
Flux<?> mergedFlux1 = Flux.merge(letters, numbers);
Flux<?> mergedFlux2 = letters.mergeWith(numbers);
ReactorLogUtil.log("mergedFlux1", mergedFlux1);
ReactorLogUtil.log("mergedFlux2", mergedFlux2);
}
输出结果:
code复制-------------------- merge()/mergeWith() --------------------
<Flux> mergedMono : [69, Tony]
<Flux> mergedFlux1 : [1, A, 2, B, 3, C]
<Flux> mergedFlux2 : [1, A, 2, B, 3, C]
关键点:
mergeWith是实例方法,merge是静态方法- 合并后的元素顺序取决于实际到达时间
- 适用于不关心顺序,只希望尽快处理元素的场景
2.1.2 concat/concatWith:顺序拼接
与merge不同,concat操作符会严格按照流的顺序拼接元素,前一个流结束后才开始下一个流。
java复制public static void demoConcat() {
ReactorLogUtil.separator("concat()/concatWith()");
Mono<String> name = Mono.just("Tony").delayElement(Duration.ofMillis(400));
Mono<String> age = Mono.just("69").delayElement(Duration.ofMillis(100));
Flux<String> letters = Flux.just("A","B","C").delayElements(Duration.ofMillis(120));
Flux<String> numbers = Flux.just("1", "2", "3").delayElements(Duration.ofMillis(100));
// Mono拼接
Flux<String> concatMono = name.concatWith(age);
ReactorLogUtil.log("concatMono", concatMono);
// Flux拼接的两种写法
Flux<String> concatFlux1 = Flux.concat(letters, numbers);
Flux<String> concatFlux2 = letters.concatWith(numbers);
ReactorLogUtil.log("concatFlux1", concatFlux1);
ReactorLogUtil.log("concatFlux2",
