带你了解 Java 中的 Mono 接口
首先要明确一点:
Java 标准库(JDK)中并没有一个叫 Mono 的接口。
你现在看到的 “Mono” 几乎 100% 指的是 Project Reactor 项目中的核心类型 reactor.core.publisher.Mono<T>,它是目前 Java 响应式编程领域使用最广泛的实现之一(与 Flux 并列)。
所以当面试官、企业内部文档、Spring 社区文章提到 “Java 中的 Mono 接口” 时,99% 的情况下指的都是 Reactor Mono。
下面用最清晰的结构带你完整理解它。
1. Mono 是什么?一句话定义
Mono 代表 “0 或 1 个元素”的异步 / 响应式序列
- 0 个元素 → 完成(onComplete)或错误(onError)
- 1 个元素 → 发出一个值后完成
- 永远不会发出第二个元素(与 Flux 最大的区别)
它对应了 Reactive Streams 规范中的 Publisher,但语义上更接近:
- Optional + Future + Publisher 的结合体
- “将来可能有 0~1 个结果”的承诺
2. Mono vs Flux 对比(最常被问)
| 维度 | Mono | Flux | 生活类比 |
|---|---|---|---|
| 元素个数 | 0 或 1 | 0 ~ N | Mono = 快递(要么到要么没到) Flux = 直播弹幕流 |
| 典型语义 | 单个结果、HTTP response、数据库单条记录 | 数据流、事件流、日志流、消息队列 | — |
| 常见操作符数量 | 约 50+ 个 | 约 150+ 个 | Flux 操作符更丰富 |
| empty() | Mono.empty() | Flux.empty() | — |
| just() | Mono.just(value) | Flux.just(v1, v2, …) | — |
| error() | Mono.error(e) | Flux.error(e) | — |
| fromCallable | 支持 | 支持 | — |
| block() | 返回 T 或 null(空时) | 返回 Iterable | 阻塞订阅 |
一句话记忆口诀:
- Mono:要么给一个,要么啥都不给(单结果承诺)
- Flux:源源不断给(多结果流)
3. Mono 最常用的创建方式(Top 10)
// 1. 直接给值
Mono.just("重阳") // Mono<String>
// 2. 空(成功但无数据)
Mono.empty() // Mono<Void> 或任何类型
// 3. 错误
Mono.error(new RuntimeException("出错了"))
// 4. 延迟创建(推荐)
Mono.fromCallable(() -> heavyComputation()) // 只有订阅时才执行
Mono.fromSupplier(() -> createExpensiveObject())
Mono.fromFuture(future) // 适配 CompletableFuture
Mono.fromCompletionStage(completionStage)
// 5. 延迟 + 调度器
Mono.delay(Duration.ofSeconds(3))
.thenReturn("3秒后出现")
// 6. 从 Optional 转换(非常常用)
Optional<String> opt = ...;
Mono<String> mono = Mono.justOrEmpty(opt);
// 7. defer(每次订阅都重新创建)
Mono.defer(() -> Mono.just(UUID.randomUUID().toString()))
// 8. 忽略值,只关心完成
Mono.from(runnableMono).then() // → Mono<Void>
4. Mono 最核心、最常考的操作符(面试高频)
| 操作符 | 作用 | 典型使用场景 |
|---|---|---|
| map / flatMap | 转换值 / 展开另一个 Mono | 数据映射、调用下游服务 |
| switchIfEmpty | 如果为空则用备选 Mono | 默认值、降级 |
| defaultIfEmpty | 如果为空则给默认值 | 简单降级 |
| filter | 过滤(不符合就 empty) | 条件筛选 |
| doOnNext / doOnError / doOnSuccess | 副作用(日志、埋点) | 调试、监控 |
| then / thenReturn | 忽略上游值,只关心完成 | 顺序执行 |
| zipWhen / zipWith | 与另一个 Mono 组合 | 并发请求合并 |
| cache | 缓存结果(后续订阅复用) | 昂贵操作结果复用 |
| retry / retryWhen | 失败重试 | 网络请求重试 |
| timeout | 超时则错误 | 防止请求挂起 |
经典组合写法示例(Spring WebFlux 常见模式)
userService.findById(id)
.switchIfEmpty(Mono.error(new NotFoundException("用户不存在")))
.flatMap(user -> orderService.findLatestOrder(user.getId()))
.map(Order::getAmount)
.defaultIfEmpty(BigDecimal.ZERO)
.doOnNext(amount -> log.info("用户 {} 最近订单金额: {}", id, amount))
.timeout(Duration.ofSeconds(5))
.onErrorResume(e -> Mono.just(BigDecimal.ZERO));
5. Mono 在 Spring 生态中的真实定位(2026 视角)
| 场景 | 返回类型通常是 | 说明 |
|---|---|---|
| Spring WebFlux Controller | Mono / Flux | 响应式 Web 核心 |
| Spring Data R2DBC | Mono / Flux | 响应式数据库客户端 |
| WebClient | Mono / Mono | 响应式 HTTP 客户端 |
| Spring Security | Mono | 响应式认证上下文 |
| Spring Cloud Function | Mono | 函数式编程风格 |
| Reactor Kafka / RabbitMQ | Flux / Mono | 响应式消息中间件 |
6. 面试最常被问的 8 个 Mono 问题(建议背熟)
- Mono 和 Flux 的本质区别是什么?
- Mono.empty() 和 Mono.just(null) 有什么不同?
- flatMap 和 map 在 Mono 上的行为差异?
- 如何让 Mono 在空时抛出自定义异常?
- Mono 和 CompletableFuture 怎么互相转换?
- block() 和 subscribe() 的区别和风险?
- 如何实现“先查缓存,没命中再查 DB,最后写回缓存”?
- timeout + retryWhen 的组合怎么写才合理?
一句话总结:
Mono 是 Reactor 体系中“单个异步结果”的标准载体,它是 Java 响应式编程世界里最优雅、最常用、最具表达力的“单值 Promise”。
如果你当前是在准备 Spring WebFlux、响应式微服务、R2DBC、WebClient 等相关面试或项目,那么 Mono 几乎是绕不开的 daily 角色。
你现在最想继续深入 Mono 的哪个部分?
- 手写 10 个最常见的 Mono 组合模式
- Mono + WebClient 的生产级最佳实践
- Mono 与 CompletableFuture / CompletionStage 的互转对比
- Mono 在错误处理上的各种姿势(onErrorResume / onErrorReturn / onErrorMap / doOnError)
- Mono vs Optional vs Future 的哲学对比
告诉我,我马上给你更细致的代码 + 原理说明。