讲师中心 微信公众号
AI工具推荐 视频效率加速

Reactor框架中非阻塞地聚合多个Flux流为单个Mono对象

老瑶小哥_2412

老瑶小哥_2412

发布时间:2025-12-03 11:58:25

|

868人浏览过

|

来源于php中文网

原创

Reactor框架中非阻塞地聚合多个Flux流为单个Mono对象

本教程深入探讨了在reactor框架中如何高效、非阻塞地将多个独立的flux流的聚合结果合并为一个单一的mono对象。通过分析传统阻塞方法的不足,文章重点介绍了mono.zipwith操作符,并结合实际业务场景,提供了详细的代码示例,旨在帮助开发者掌握响应式编程中聚合操作的最佳实践,确保应用的非阻塞性和响应性。

响应式流聚合的挑战

在基于Project Reactor构建的响应式应用中,我们经常需要处理来自不同异步源的数据流,并将它们聚合到一个统一的响应式对象中。例如,您可能需要从两个独立的Flux流中收集元素列表,然后将这两个列表封装到一个最终的Mono对象中。然而,如果不正确地处理这些流,很容易引入阻塞操作,从而违背响应式系统的核心原则。

考虑以下业务场景和数据模型,我们需要收集成功账户和失败账户的列表,并将它们组合成一个Payments对象:

package org.example;

import lombok.Builder;
import lombok.Getter;
import lombok.ToString;

import java.util.List;

@Getter
@Builder
@ToString
public class Payments {
    private List<SuccessAccount> successAccounts;
    private List<FailedAccount> failedAccounts;

    @Getter
    @Builder
    @ToString
    public static class SuccessAccount {
        private String name;
        private String accountNumber;
    }

    @Getter
    @Builder
    @ToString
    public static class FailedAccount {
        private String name;
        private String accountNumber;
        private String errorCode;
    }
}

我们的目标是获取 Flux 和 Flux,并将它们聚合到一个 Mono 对象中,整个过程必须是非阻塞的。

传统阻塞方法的陷阱

初学者在处理此类聚合需求时,可能会尝试将每个 Flux 通过 collectList() 转换为 Mono>,然后立即订阅这些 Mono 来收集数据。例如:

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.List;

// 这是一个错误的阻塞示例
public class MainBlockingExample {

    // 模拟获取成功账户的Flux
    public static Flux<Payments.SuccessAccount> getAccountsSucceeded() { /* ... */ return Flux.empty(); }
    // 模拟获取失败账户的Flux
    public static Flux<Payments.FailedAccount> getAccountsFailed() { /* ... */ return Flux.empty(); }

    public static Mono<Payments> getPaymentDataBlocking() {
        Flux<Payments.SuccessAccount> accountsSucceeded = getAccountsSucceeded();
        Flux<Payments.FailedAccount> accountsFailed = getAccountsFailed();

        List<Payments.SuccessAccount> successAccounts = new ArrayList<>();
        List<Payments.FailedAccount> failedAccounts = new ArrayList<>();

        // 这里的subscribe方法在主线程中执行时,会阻塞当前线程直到数据收集完成
        // 这与响应式编程的非阻塞原则相悖。在实际业务场景中,如果方法需要返回Mono
        // 而不是在内部等待结果,这种方式是不可取的。
        accountsFailed.collectList().subscribe(failedAccounts::addAll);
        accountsSucceeded.collectList().subscribe(successAccounts::addAll);

        return Mono.just(Payments.builder()
                .failedAccounts(failedAccounts)
                .successAccounts(successAccounts)
                .build());
    }
}

尽管 collectList() 返回的是一个 Mono,但直接在其上调用 subscribe() 并试图在订阅回调中同步收集数据,在需要返回 Mono 的方法内部是典型的反模式。这会导致当前线程阻塞,等待 subscribe 操作完成,从而失去了响应式编程的非阻塞优势。在Spring WebFlux控制器等场景中,我们期望方法直接返回一个 Mono 或 Flux,而不是在方法内部阻塞等待结果。

解决方案:使用 Mono.zipWith 进行非阻塞聚合

Reactor 提供了 zip 操作符,专门用于将多个发布者(Publisher)的元素按照索引进行组合。当所有参与 zip 操作的发布者都发出一个元素时,zip 才会将这些元素组合成一个元组(Tuple)并向下游发出。对于聚合两个 Mono> 的场景,Mono.zipWith 是理想的选择。

Java Api Extractor
Java Api Extractor

从 Java 项目中提取 Controller 层接口定义为 JSON 数据。

下载

1. 将 Flux 转换为 Mono

首先,我们需要将每个 Flux 转换为一个 Mono>。collectList() 操作符正是为此而生,它会收集 Flux 中的所有元素,并在 Flux 完成时,将这些元素作为一个 List 封装在 Mono 中发出。

// 获取成功的账户流
Flux<Payments.SuccessAccount> accountsSucceeded = getAccountsSucceeded();
// 获取失败的账户流
Flux<Payments.FailedAccount> accountsFailed = getAccountsFailed();

// 将Flux转换为Mono<List>
Mono<List<Payments.FailedAccount>> failedAccountsMono = accountsFailed.collectList();
Mono<List<Payments.SuccessAccount>> successAccountsMono = accountsSucceeded.collectList();

2. 使用 Mono.zipWith 组合结果

现在,我们有了两个 Mono 对象,分别持有失败账户列表和成功账户列表。接下来,可以使用 Mono.zipWith 将它们组合起来。zipWith 方法接受另一个 Mono 和一个 BiFunction。BiFunction 定义了如何将这两个 Mono 发出的值组合成一个新值。

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public class Main {
    // ... (getAccountsSucceeded 和 getAccountsFailed 方法定义)

    /**
     * 非阻塞地聚合成功账户和失败账户列表到Payments对象。
     *
     * @return 包含Payments对象的Mono
     */
    public static Mono<Payments> getPaymentDataNonBlocking() {
        Flux<Payments.SuccessAccount> accountsSucceeded = getAccountsSucceeded();
        Flux<Payments.FailedAccount> accountsFailed = getAccountsFailed();

        return accountsFailed.collectList() // 第一个Mono:Mono<List<FailedAccount>>
                .zipWith(
                        accountsSucceeded.collectList(), // 第二个Mono:Mono<List<SuccessAccount>>
                        (failedList, successList) -> Payments.builder() // BiFunction组合两个列表
                                .failedAccounts(failedList)
                                .successAccounts(successList)
                                .build()
                );
    }
}

在这个解决方案中,accountsFailed.collectList() 和 accountsSucceeded.collectList() 会并行地收集各自的账户列表。一旦两个 Mono 都完成了其内部的列表收集并发出值,zipWith 的 BiFunction 就会被调用,使用这两个列表构建 Payments 对象,并将其封装在一个新的 Mono 中发出。整个过程是完全非阻塞的。

完整示例代码

为了更好地理解和实践,以下是包含 Payments 模型和 Main 类的完整示例:

package org.example;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.List;

// Payments.java (与前面定义相同,此处省略重复代码)
/*
@Getter
@Builder
@ToString
public class Payments {
    private List<SuccessAccount> successAccounts;
    private List<FailedAccount> failedAccounts;

    @Getter
    @Builder
    @ToString
    public static class SuccessAccount {
        private String name;
        private String accountNumber;
    }

    @Getter
    @Builder
    @ToString
    public static class FailedAccount {
        private String name;
        private String accountNumber;
        private String errorCode;
    }
}
*/

public class Main {
    public static void main(String[] args) {
        // 订阅并打印结果,这是在应用启动时触发流的示例
        getPaymentDataNonBlocking().subscribe(
            System.out::println, // 成功处理
            error -> System.err.println("Error: " + error.getMessage()), // 错误处理
            () -> System.out.println("Processing complete.") // 完成信号
        );

        // 为了演示异步性,

热门AI工具

更多
UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

立刻MV
立刻MV Hot

立刻MV是一款AI文本写作工具,AI 音乐视频(MV)创作工具。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

小橡皮AI

一款AI工具,主要用于小橡皮一键去AI味,将机器味重的内容改得更有活人感,防止因为 AI 内容被平台打上 AI 标签,以及限流,适合需要提升相关任务效率的用户。

蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

相关专题

更多
spring框架介绍
spring框架介绍

本专题整合了spring框架相关内容,想了解更多详细内容,请阅读专题下面的文章。

2751

2025.08.06

Java Spring Security 与认证授权
Java Spring Security 与认证授权

本专题系统讲解 Java Spring Security 框架在认证与授权中的应用,涵盖用户身份验证、权限控制、JWT与OAuth2实现、跨站请求伪造(CSRF)防护、会话管理与安全漏洞防范。通过实际项目案例,帮助学习者掌握如何 使用 Spring Security 实现高安全性认证与授权机制,提升 Web 应用的安全性与用户数据保护。

497

2026.01.26

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

4478

2023.08.10

Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

60

2026.10.28

Vite 如何搭配 Vue.js
Vite 如何搭配 Vue.js

Vite搭配Vue.js专题汇集创建Vue 3项目、配置开发环境、路径别名、Vue插件、代理、多环境构建与调试的已审核文章,帮助读者完成从初始化到日常开发的配置。

20

2026.10.28

Vite 项目目录怎么看
Vite 项目目录怎么看

Vite项目目录专题汇集入口文件、src源码、public静态资源、路径别名、项目根目录和构建输出目录相关的已审核文章,帮助读者理解各文件夹的用途与引用方式。

40

2026.10.28

Gradle插件开发入门教程
Gradle插件开发入门教程

本专题介绍Gradle插件开发的基本流程,涵盖插件类编写、自定义任务注册、扩展参数设计和插件应用方法,帮助你理解插件如何组织构建逻辑,减少多个项目之间的重复配置。

140

2026.10.22

Gradle Wrapper配置教程
Gradle Wrapper配置教程

本专题介绍Gradle Wrapper的基本作用,涵盖文件组成、生成方法、版本配置和任务调用,帮助你理解项目如何使用指定的Gradle版本,减少不同开发环境带来的构建差异。

80

2026.10.22

Golangci-lint检查器使用教程
Golangci-lint检查器使用教程

本专题整理Golangci-lint常用Linters的用途与配置方法,覆盖错误处理、无效代码、安全、复杂度、命名和依赖检查,帮助项目合理选规则。

80

2026.10.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 12.4万人学习

国外Web开发全栈课程全集
国外Web开发全栈课程全集

共12课时 | 1.4万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn