WebFlux中Mono空场景处理:flatMap与delayUntil差异解析
WebFlux分支执行逻辑疑惑解答
基础代码定义
static Mono<String> init(String input) { if (input == null) { return Mono.empty(); } if ("err".equals(input)) { throw new IllegalArgumentException("Some exception"); } System.out.println("First Mono: " + input); return Mono.just("First Mono: " + input); } static Mono<Void> doesSomeStuff(String input) { System.out.println("Second Mono: " + input); return Mono.empty(); }
首次尝试(不符合预期)
/** * Wrong! The 3rd line should not be shown! Defer causes empty block to be lazily evaluated * * First Mono: Hello * Second Mono: First Mono: Hello * Second Mono: Empty */ Mono<String> firstMono = init("Hello"); firstMono.flatMap(WebfluxDemoApplication::doesSomeStuff) .switchIfEmpty(Mono.defer(() -> doesSomeStuff("Empty"))) .block(); /** * Correct! Only the empty branch is executed * * Second Mono: Empty */ Mono<String> first2Mono = init(null); first2Mono.flatMap(WebfluxDemoApplication::doesSomeStuff) .switchIfEmpty(Mono.defer(() -> doesSomeStuff("Empty"))) .block();
正确实现(使用delayUntil+switchIfEmpty+then)
/** * Correct! * * First Mono: Hello * Second Mono: First Mono: Hello */ Mono<String> first5Mono = init("Hello"); first5Mono.delayUntil(initVal -> doesSomeStuff(initVal)) .switchIfEmpty(Mono.defer(() -> doesSomeStuff("Empty")).cast(String.class)) .then() .block(); System.out.println("---"); /** * Correct! * * Second Mono: Empty */ Mono<String> first6Mono = init(null); first6Mono.delayUntil(initVal -> doesSomeStuff(initVal)) .switchIfEmpty(Mono.defer(() -> doesSomeStuff("Empty")).cast(String.class)) .then() .block(); System.out.println("---"); /** * Correct! It throws the Exception. * * Second Mono: Empty */ Mono<String> first7Mono = init("err"); first7Mono.delayUntil(initVal -> doesSomeStuff(initVal)) .switchIfEmpty(Mono.defer(() -> doesSomeStuff("Empty")).cast(String.class)) .then() .block();
问题解答
1. 为什么delayUntil可行而flatMap不行?
核心差异在于两者对上游信号的处理逻辑:
- flatMap的问题:
flatMap会将上游信号转换为新的Mono,这里doesSomeStuff返回Mono<Void>且内部是Mono.empty(),所以当上游init("Hello")非空时,flatMap最终输出的是一个空的Mono<Void>,这个空信号直接触发了后续的switchIfEmpty,导致回退任务被执行。 - delayUntil的优势:
delayUntil仅等待传入的Mono完成,但会完整保留上游的原始信号类型和值。上游init("Hello")返回的Mono<String>非空,执行完delayUntil里的任务后,依然向下游传递原始的非空信号,不会触发switchIfEmpty。
2. switchIfEmpty中为何需要cast操作?
switchIfEmpty要求传入的Mono类型必须与上游Mono完全一致:
- 上游的
first5Mono/first6Mono是Mono<String>,但doesSomeStuff("Empty")返回的是Mono<Void>,类型不匹配会导致编译错误。 .cast(String.class)是为了将Mono<Void>强制转换为Mono<String>,让类型检查通过。由于doesSomeStuff仅执行打印逻辑,实际不会传递任何值,这个转换不会引发运行时问题。
3. then的作用是否因doesSomeStuff返回Mono才生效?
不是,then的核心作用与上游Mono的类型无关:
then的本质是忽略上游所有的信号值,只等待整个序列执行完成,最终返回一个Mono<Void>。- 在当前场景中使用
then,是为了统一最终的返回类型,同时确保整个链式调用的信号流完整执行完毕。即使doesSomeStuff返回其他类型的Mono,then依然能起到收尾、丢弃上游元素的作用。
内容的提问来源于stack exchange,提问作者user1927638
相关产品推荐
相关产品推荐

