Mono 是响应式流中表示“0 或 1 个异步结果”的不可变发布者类型;而 MonoSink 是创建自定义 Mono 时用于手动控制数据发射(success/error/cancel)的可变回调句柄,仅在 Mono.create() 场景下短暂存在。二者角色截然不同:Mono 是面向消费者的最终产物,MonoSink 是面向开发者的构造工具。
mono 是响应式流中表示“0 或 1 个异步结果”的不可变发布者类型;而 monosink 是创建自定义 mono 时用于手动控制数据发射(success/error/cancel)的可变回调句柄,仅在 `mono.create()` 场景下短暂存在。二者角色截然不同:mono 是面向消费者的最终产物,monosink 是面向开发者的构造工具。
在 Project Reactor 中,Mono 和 MonoSink 虽同属 reactor.core.publisher 包,但语义、生命周期与使用场景有根本性差异——理解这一点是写出清晰、可维护响应式代码的关键。
✅ Mono:声明式、不可变、面向消费的“承诺”
Mono<T> 是一个 Reactive Streams Publisher,代表一个最多发出 0 或 1 个元素的异步序列。它遵循“冷信号”语义:定义即创建,但不执行;只有被订阅(subscribe())或触发终端操作(如 block()、toFuture())时才真正运行。它是不可变的、声明式的抽象,设计目标是被组合、被转换、被消费。
常见用法(均为工厂方法,返回已构建好的 Mono 实例):
// ✅ 立即声明,惰性执行
Mono<String> hello = Mono.just("Hello"); // 发出单个值
Mono<Void> empty = Mono.empty(); // 发出 onComplete
Mono<Integer> error = Mono.error(new RuntimeException("Boom")); // 发出 onError
Mono<Long> delay = Mono.delay(Duration.ofSeconds(2)); // 延迟完成(无值)
Mono<User> user = userRepository.findById(123L); // 典型业务调用(返回 Mono)所有这些 Mono 实例均可链式调用操作符(map, flatMap, filter, onErrorResume 等),且每次调用都返回新的 Mono 实例,原实例不受影响——这正是其不可变性的体现。
立即学习“Java免费学习笔记(深入)”;
✅ MonoSink:命令式、可变、面向构造的“发射控制器”
MonoSink 并非独立的数据容器,而是 Mono.create() 方法内部提供的一次性、线程安全的回调句柄,用于在自定义异步逻辑中主动触发信号:
- sink.success(value) → 发出 1 个元素并完成(onNext + onComplete)
- sink.error(throwable) → 发出错误信号(onError)
- sink.cancel() → 取消订阅(通常由下游主动触发)
⚠️ 关键限制:
- MonoSink 只能被调用一次(多次 success() 或 error() 将抛出 IllegalStateException);
- 它不暴露给下游消费者,仅作用于 create() 的 lambda 内部;
- 它的存在是为了桥接非响应式、多线程、回调驱动的旧有 API(如传统异步 SDK、事件监听器、Socket 回调等)。
典型使用场景示例:
// ✅ 将基于回调的老式 HTTP 客户端包装为 Mono
public Mono<String> legacyHttpGet(String url) {
return Mono.create(sink -> {
legacyHttpClient.get(url, new Callback() {
@Override
public void onSuccess(String response) {
sink.success(response); // ✅ 安全发射成功结果
}
@Override
public void onFailure(Exception e) {
sink.error(e); // ✅ 安全发射错误
}
});
});
}
// ❌ 错误示范:在 sink 外部持有或重复使用
// private MonoSink<String> badSink; // 编译可能通过,但运行时必崩? 对比总结:一张表看本质差异
| 维度 | Mono<T> | MonoSink<T> |
|---|---|---|
| 角色 | 数据流的声明式抽象(Publisher) | Mono.create() 的内部发射控制器 |
| 生命周期 | 可长期持有、复用、组合、订阅 | 仅在 create() lambda 内有效,一次性使用 |
| 可变性 | 不可变(操作符返回新实例) | 可变(调用 success()/error() 改变状态) |
| 线程安全 | 操作符链天然线程安全 | sink.success() 等方法本身线程安全 |
| 何时出现 | 几乎所有响应式业务代码中 | 仅在需要手动桥接非响应式异步源时 |
| 类比 | Optional<T>(但支持异步+背压) | CompletableFuture.complete() 的响应式版 |
? 注意事项与最佳实践
- 优先使用工厂方法:95% 的场景应使用 Mono.just(), Mono.fromCallable(), Mono.fromFuture() 等,而非 create() —— 它们更安全、更简洁、自动处理异常和线程上下文。
- 慎用 create():仅当必须集成无 Publisher 接口的遗留异步系统时才考虑;务必确保 sink 调用严格满足“至多一次 success/error”约束。
- 避免泄露 sink:切勿将 MonoSink 作为字段或参数跨作用域传递,这会破坏响应式契约并导致难以调试的竞态问题。
- MonoSink ≠ Mono:声明 private final MonoSink<MyModel1> sink; 是反模式——你实际需要的是 private final Mono<MyModel1> mono;;前者只应在 create() 的匿名函数内瞬时存在。
简言之:Mono 是你交付给调用方的“产品”,MonoSink 是你在工厂流水线上用来组装该产品的“专用扳手”。 正确区分二者,才能写出既符合响应式哲学、又稳健可靠的 Java 代码。


















