Mono.fromFuture和Mono.fromSupplier
刚开始尝试使用 Spring WebFlux 的时候,很多人都会使用 Mono.fromFuture() 将异步请求转成 Mono 对象,或者 Mono.fromSupplier() 将请求转成 MOno 对象,这两种方式在响应式编程 中都是不建议的,都会阻塞当前线程。
Mono.fromFuture() VS WebClient
Mono.fromFuture()方法和使用 WebClient 调用第三方接口之间存在以下区别:
异步 vs. 非阻塞
Mono.fromFuture()方法适用于接收一个 java.util.concurrent.Future 对象,并将其转换为响应式的 Mono。这是一个阻塞操作,因为它会等待 Future 对象完成。而使用 WebClient 调用第三方接口是异步和非阻塞的,它不会直接阻塞应用程序的执行,而是使用事件驱动的方式处理响应。
可扩展性和灵活性:使用 WebClient 可以更灵活地进行配置和处理,例如设置超时时间、请求头、重试机制等。WebClient 还可以与许多其他 Spring WebFlux 组件集成,如 WebSockets、Server-Sent Events 等。而 Mono.fromFuture() 是适用于单个 Future 对象转化为 Mono 的情况,可扩展性较差。
错误处理
WebClient 提供了更丰富的错误处理机制,可以通过 onStatus、onError 等方法来处理不同的 HTTP 状态码或异常。同时,WebClient 还提供了更灵活的重试和回退策略。Mono.fromFuture() 方法只能将 Future 对象的结果包装在 Mono 中,不提供特定的错误处理机制。
阻塞操作
Mono.fromFuture() 会阻塞。当调用 Mono.fromFuture() 方法将 Future 转换为 Mono 时,它会等待 Future 对象的结果返回。在这个等待的过程中,Mono.fromFuture()方法会阻塞当前的线程。这意味着,如果 Future 的结果在运行过程中没有返回,则当前线程会一直阻塞,直到 Future 对象返回结果或者超时。因此,在使用 Mono.fromFuture() 时需要注意潜在的阻塞风险。另外,需要确保F uture 的任务在后台线程中执行,以免阻塞应用程序的主线程。
Mono.fromFuture VS Mono.fromSupplier
Mono.fromSupplier() 和 Mono.fromFuture() 都是用于将异步执行的操作转换为响应式的 Mono 对象,但它们的区别在于:
Mono.fromSupplier() 适用于一个提供者/生产者,可以用来表示某个操作的结果,该操作是一些纯计算并且没有阻塞的方法。也就是说,Mono.fromSupplier() 将其参数 (Supplier) 所提供的操作异步执行,并将其结果打包成一个 Mono 对象。
Mono.fromFuture() 适用于一个 java.util.concurrent.Future 对象,将其封装成 Mono 对象。这意味着调用 Mono.fromFuture() 方法将阻塞当前线程,直到异步操作完成返回一个 Future 对象。
因此,Mono.fromSupplier() 与 Mono.fromFuture() 的主要区别在于:
Mono.fromSupplier() 是一个非阻塞的操作,不会阻塞当前线程。这个方法用于执行计算型的任务,返回一个封装了计算结果的 Mono 对象。
Mono.fromFuture() 是阻塞操作,会阻塞当前线程,直到异步操作完毕并返回看,它适用于处理 java.util.concurrent.Future 对象。
需要注意的是,如果 Supplier 提供的操作是阻塞的,则 Mono.fromSupplier() 方法本身也会阻塞线程。但通常情况下,Supplier 提供的操作是纯计算型的,不会阻塞线程。
因此,可以使用 Mono.fromSupplier() 方法将一个纯计算型的操作转换为 Mono 对象,而将一个异步返回结果的操作转换为 Mono 对象时,可以使用 Mono.fromFuture() 方法。
Mono.fromCallable
在 Spring WebFlux(基于 Project Reactor)中,Mono.fromCallable() 是一个非常核心的操作符,主要用于桥接同步阻塞代码到响应式流中 ,并实现延迟执行。
- 核心作用
- 延迟执行 (Lazy Evaluation) :与
Mono.just()不同,fromCallable里的代码只有在有订阅者(Subscriber)时才会执行。 - 异常包装 :它能自动捕获
Callable抛出的受检异常(Checked Exception),并将其转换为响应式流中的错误信号(Error Signal),让你可以通过onErrorResume等操作符进行统一处理。 - 空值处理 :如果
Callable返回null,它会产生一个空的Mono(类似于Mono.empty()),而不会像Mono.just(null)那样抛出空指针异常。
- 代码对比:
justvsfromCallable
| 特性 | Mono.just(method()) |
Mono.fromCallable(() -> method()) |
|---|---|---|
| 执行时机 | 立即执行(在创建 Mono 时) | 订阅时执行(Lazy) |
| 适用场景 | 已知的结果、常量 | 数据库查询、文件读写、耗时计算 |
| 线程阻塞 | 可能会阻塞主线程/事件循环 | 配合 subscribeOn 可以切换到专门的线程池 |
- 典型使用场景:处理阻塞操作
WebFlux 的事件循环(Event Loop)绝对不能被阻塞。如果你必须调用传统的阻塞式 API(如 JDBC 或旧的 RestTemplate),你应该按照以下模式包装:
public Mono<String> getLegacyData(String id) {
return Mono.fromCallable(() -> {
// 这里是阻塞操作,例如旧的 DB 驱动或 RestTemplate 调用
return blockingService.fetchData(id);
})
// 关键:将阻塞任务交给专门的线程池,避免拖垮 WebFlux 的事件循环
.subscribeOn(Schedulers.boundedElastic());
}
Schedulers.boundedElastic():这是官方推荐用于 I/O 阻塞任务的线程池,它会根据需要动态扩展,但有上限,防止创建过多线程导致 OOM。
- 常见问题
- 返回空值 :如果你想从
fromCallable返回"无结果",直接return null。下游会收到onComplete信号而非错误。 - 与
defer的区别 :Mono.defer()也是延迟执行,但它要求 Lambda 返回一个Publisher(如Mono),而fromCallable返回的是普通对象。如果你已经有一个返回Mono的阻塞方法,通常用defer;如果你只有一段简单的同步逻辑,用fromCallable更简洁。
建议: 如果你正在使用 Java 21+ 且对响应式编程感到复杂,也可以考虑 虚拟线程 (Virtual Threads),它允许你在传统的 Spring MVC 中以阻塞风格编写高性能代码,而无需这种繁琐的包装。
Mono.defer
Mono.defer 是 Project Reactor(Spring WebFlux 的核心库)中的一个静态方法,用于延迟 Mono 的创建,直到有订阅者真正订阅它时才会执行。
- 核心作用:懒加载 (Lazy Evaluation)
在响应式编程中,大多数操作符是"声明式"的。如果你直接使用 Mono.just(someMethod()),someMethod() 会在组装流水线时 (即代码执行到这一行时)立即执行。
而 Mono.defer(() -> someMethod()) 会将该方法的执行推迟到订阅发生时。
- 为什么需要它?(常见场景)
- 确保获取最新数据 :
如果你需要返回当前时间,Mono.just(System.currentTimeMillis())记录的是 Mono 创建的时间。使用Mono.defer则能保证每个订阅者拿到的都是订阅那一刻的系统时间。 - 配合
switchIfEmpty或onErrorResume使用 :
这是最经典的坑。在mono.switchIfEmpty(fallbackMethod())中,即使mono不为空,fallbackMethod()也会在初始化阶段被调用。为了避免不必要的资源消耗或副作用,应该写成:
.switchIfEmpty(Mono.defer(() -> fallbackMethod()))。 - 处理有副作用或阻塞的代码 :
当一个方法内部逻辑较重或涉及 IO 时,直接调用它返回Mono可能会导致在流水线组装期间就产生开销。通过defer可以确保这些逻辑只在真正需要数据时才跑。
- 代码对比示例
| 方法 | 执行时机 (Assembly vs Subscription) | 结果一致性 |
|---|---|---|
Mono.just(val) |
立即执行:在代码定义处就确定了值 | 所有订阅者共享同一个旧值 |
Mono.defer(() -> Mono.just(val)) |
订阅时执行:每次订阅都会重新运行 Lambda | 每个订阅者都能触发一次逻辑,获取新值 |
- 与
fromSupplier的区别
Mono.fromSupplier:期待 Lambda 返回一个普通对象T。Mono.defer:期待 Lambda 返回另一个Mono<T>。当你调用的第三方方法本身就返回Mono类型时,通常只能用defer来实现延迟。