
在 Reactor 中,Mono 的 doOnSuccess 会在空完成(empty completion)时也被触发,无法区分“操作实际执行”与“被过滤跳过”。正确方式是将副作用逻辑嵌入 onSuccess 链内,确保仅当 loadSmth() 真正完成时才触发后续动作。
在 reactor 中,`mono
在响应式编程中,Mono<void></void> 表示一个不发射数据、仅表达“完成”或“错误”的异步操作。但其语义上的“完成”包含两种情形:一是下游操作真实执行并成功结束;二是上游被 filter 等操作提前终止为 empty——此时 Mono<void></void> 仍会以“成功但无元素”的形式完成,导致 doOnSuccess、then() 或 doOnTerminate 无法可靠区分二者。
你遇到的问题正是典型陷阱:
.flatMap(msg -> service.loadSmth(msg)) .doOnSuccess(v -> serviceContinueWork.doSmthElse()) // ❌ 错误:empty 也会触发
因为 doOnSuccess 对 Mono<void></void> 的语义是“流正常终止”,而非“有实际工作发生”。
✅ 正确解法是将副作用绑定到具体操作的生命周期内,即使用 onSuccess(Runnable)(注意:这是 Mono 的实例方法,非操作符)——它仅在该 Mono 非空且成功完成时执行:
public Mono<Void> execute(String resource) {
return Mono.just(resource)
.filter(Objects::nonNull)
.filter(msg -> msg.startsWith("http://"))
.flatMap(msg -> service.loadSmth(msg)
.onSuccess(() -> serviceContinueWork.doSmthElse())) // ✅ 仅当 loadSmth 实际完成才调用
.doOnError(t -> log.error("service#loadSmth error: {}", ExceptionUtils.getStackTrace(t)))
.then();
}⚠️ 关键细节说明:
-
onSuccess(Runnable)是Mono类的终端消费方法(属于Mono实例,非链式操作符),它不会改变流结构,只在满足条件时触发副作用; - 它与
doOnSuccess的根本区别在于:doOnSuccess响应整个流的完成信号(含 empty),而onSuccess(Runnable)仅对非空成功事件生效; - 此方案无需修改
service.loadSmth()的签名(保持Mono<void></void>),符合“不为测试/监控侵入业务接口”的设计原则; - 若需处理异常后清理,可搭配
onErrorResume或doFinally,但doFinally仍无法区分空完成与真实完成,故不适用于本场景。
? 进阶提示:若需更精细控制(如记录是否跳过、统计执行率),可改用 handle() 操作符显式建模三种状态(skip / success / error),但对多数场景,onSuccess() 已足够简洁可靠。

















