别再混淆了!用5个代码示例讲清楚Reactor Core的Flux和Mono到底怎么选
别再混淆了用5个代码示例讲清楚Reactor Core的Flux和Mono到底怎么选刚接触Reactor Core的Java开发者往往会被Flux和Mono这两个核心类型搞得晕头转向。什么时候该用Flux什么时候该用Mono为什么同样的操作有时用Flux有时用Mono这些问题困扰着不少响应式编程的初学者。今天我们就用5个实际开发中最常见的场景通过对比错误用法和推荐用法的代码示例帮你彻底理清Flux和Mono的选择逻辑。读完本文后你将能够准确判断何时使用Flux何时使用Mono理解两种类型的转换场景和方法避免常见的误用陷阱建立类型选择的直觉1. 数据库查询单条记录 vs 多条记录数据库操作是响应式编程中最常见的场景之一。我们先来看一个典型问题查询数据库时返回单条记录和返回多条记录分别该用什么类型错误用法// 查询单个用户 FluxUser getUserById(Long id) { return userRepository.findById(id); } // 查询所有用户 MonoListUser getAllUsers() { return userRepository.findAll().collectList(); }推荐用法// 查询单个用户 - 使用Mono MonoUser getUserById(Long id) { return userRepository.findById(id); } // 查询所有用户 - 使用Flux FluxUser getAllUsers() { return userRepository.findAll(); }关键区别查询单条记录时结果可能是0或1个元素这正是Mono的语义0..1查询多条记录时结果是0到N个元素这正是Flux的语义0..N不要将Flux强制转换为MonoList 这会破坏响应式流的特性2. HTTP服务调用单个响应 vs 流式响应调用外部HTTP服务是另一个常见场景。现代HTTP客户端如WebClient都支持响应式编程那么该如何选择返回类型呢错误用法// 获取单个产品详情 FluxProduct getProductDetail(Long productId) { return webClient.get() .uri(/products/{id}, productId) .retrieve() .bodyToFlux(Product.class); } // 获取产品列表 MonoListProduct getProductList() { return webClient.get() .uri(/products) .retrieve() .bodyToFlux(Product.class) .collectList(); }推荐用法// 获取单个产品详情 - 使用Mono MonoProduct getProductDetail(Long productId) { return webClient.get() .uri(/products/{id}, productId) .retrieve() .bodyToMono(Product.class); } // 获取产品列表 - 使用Flux FluxProduct getProductList() { return webClient.get() .uri(/products) .retrieve() .bodyToFlux(Product.class); }最佳实践当API明确返回单个对象时使用bodyToMono()当API返回数组或流式数据时使用bodyToFlux()只有在确实需要将所有元素收集到内存中时才使用collectList()3. 可能为空的结果处理处理可能为空的结果是响应式编程中的一个重要话题。我们来看看如何正确处理这种情况。错误用法MonoUser findUserByName(String name) { return userRepository.findByName(name) .switchIfEmpty(Flux.error(new NotFoundException())); }推荐用法MonoUser findUserByName(String name) { return userRepository.findByName(name) .switchIfEmpty(Mono.error(new NotFoundException())); }重要原则保持类型一致性不要混用Flux和Mono的操作符对于可能为空的情况Mono提供了专门的操作符defaultIfEmpty()switchIfEmpty()onErrorReturn()使用Flux来处理空情况会引入不必要的复杂性4. 类型转换何时以及如何转换在实际开发中我们经常需要在Flux和Mono之间进行转换。下面介绍几种常见场景。场景1Flux → Mono// 统计用户数量 MonoLong countUsers() { return userRepository.findAll() .count(); } // 检查用户是否存在 MonoBoolean userExists(String email) { return userRepository.findByEmail(email) .hasElement(); }场景2Mono → Flux// 获取用户的所有订单 FluxOrder getUserOrders(Long userId) { return userRepository.findById(userId) .flatMapMany(user - orderRepository.findByUserId(user.getId())); }转换方法总结转换方向常用操作符适用场景Flux → MonocollectList(), count(), reduce()聚合操作、统计Mono → FluxflatMapMany(), expand()展开嵌套的响应式流5. 错误处理策略Flux和Mono在错误处理上有一些细微但重要的区别了解这些区别可以避免很多陷阱。错误处理对比// Mono的错误处理 MonoUser getUserWithFallback(Long id) { return userRepository.findById(id) .onErrorResume(e - Mono.just(User.anonymous())); } // Flux的错误处理 FluxUser getActiveUsersWithFallback() { return userRepository.findByActiveTrue() .onErrorResume(e - Flux.just(User.anonymous())); }错误处理指南资源清理对于Mono使用doFinally()清理资源对于Flux考虑使用using()操作符管理资源生命周期重试策略retry()简单重试retryWhen()带延迟的重试两者都适用于Flux和Mono但实现细节有所不同超时处理使用timeout()操作符对于Flux注意超时会影响整个流而不仅仅是单个元素实战经验分享在实际项目中使用Flux和Mono一段时间后我总结出几个特别有用的经验类型选择快速决策树结果是0或1个元素 → 用Mono结果是0到N个元素 → 用Flux不确定 → 考虑API的语义而非实现细节性能考量对于已知的单个结果使用Mono比Flux更高效对于大型数据集Flux的流式处理可以显著降低内存使用测试技巧使用StepVerifier测试Flux/Mono特别注意验证complete和error信号对于Flux验证元素顺序有时很重要调试技巧使用log()操作符记录流事件对于复杂流考虑使用checkpoint()添加调试标记Reactor Debug Agent是调试神器