在Reactor响应式编程框架中,Mono.expand是一个非常实用的操作符,它能够基于初始的Mono对象,通过递归展开的方式逐步生成后续的关联数据,最终将单个Mono转换为包含完整链式查询结果的Flux序列,非常适合处理需要逐层查询关联数据的业务场景。

Mono.expand的核心作用
Mono.expand的核心逻辑是接收一个Function<T, Publisher<T>>类型的函数作为参数,这个函数会定义如何根据当前元素生成下一个关联元素。框架会先发射初始的Mono元素,然后将这个元素传入函数生成下一个Publisher,再发射该Publisher中的元素,以此类推,直到生成的元素对应的Publisher不再发射新元素,最终将所有发射的元素组合成一个Flux返回。
简单来说,它解决的是单起点、多层级关联数据的链式查询问题,比如根据用户信息查询其上级用户,再查询上级的上级,直到没有上级为止,这类场景用Mono.expand实现会非常简洁。
基础使用示例
假设我们有一个用户实体,每个用户有一个上级用户ID,我们需要根据某个用户ID,查询出该用户及其所有上级用户组成的序列。首先定义用户实体类:
public class User {
private Long id;
private String name;
private Long parentId;
// 构造方法、getter、setter省略
}
然后模拟一个根据用户ID查询用户的服务方法,实际场景中可能是调用数据库或者远程接口:
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
public class UserService {
// 模拟用户数据存储
private static final Map<Long, User> USER_MAP = new HashMap<>();
static {
USER_MAP.put(1L, new User(1L, "用户A", 2L));
USER_MAP.put(2L, new User(2L, "用户B", 3L));
USER_MAP.put(3L, new User(3L, "用户C", null));
USER_MAP.put(4L, new User(4L, "用户D", null));
}
// 根据用户ID查询用户,不存在返回empty
public Mono<User> getUserById(Long id) {
User user = USER_MAP.get(id);
return user == null ? Mono.empty() : Mono.just(user);
}
}
接下来使用Mono.expand实现链式查询用户及其所有上级的功能:
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class ExpandDemo {
public static void main(String[] args) {
UserService userService = new UserService();
// 从ID为1的用户开始展开查询
Flux<User> userFlux = Mono.just(1L)
// 先查询初始用户
.flatMap(userService::getUserById)
// 使用expand展开查询上级
.expand(user -> {
// 如果用户有上级ID,查询上级用户,否则返回empty终止展开
if (user.getParentId() != null) {
return userService.getUserById(user.getParentId());
} else {
return Mono.empty();
}
});
// 订阅输出结果
userFlux.subscribe(user -> System.out.println("用户ID:" + user.getId() + ", 用户名:" + user.getName()));
}
}
运行上述代码,输出结果如下:
用户ID:1, 用户名:用户A 用户ID:2, 用户名:用户B 用户ID:3, 用户名:用户C
使用注意事项
1. 必须设置终止条件
expand的操作是递归展开的,如果没有正确的终止条件,会导致无限递归,最终引发栈溢出或者内存溢出问题。上面的示例中通过判断parentId是否为null来决定是否返回empty,就是明确的终止条件。
2. 与flatMapMany的区别
很多开发者会混淆expand和flatMapMany,两者的核心差异在于:flatMapMany是将当前元素转换为一个Publisher,直接发射该Publisher的所有元素后就结束,不会基于后续元素继续展开;而expand会基于每个新发射的元素继续调用展开函数,生成新的元素,直到满足终止条件。
比如下面的flatMapMany示例,只会查询初始用户和第一层上级,不会继续查询上级的上级:
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class FlatMapManyDemo {
public static void main(String[] args) {
UserService userService = new UserService();
Flux<User> userFlux = Mono.just(1L)
.flatMap(userService::getUserById)
.flatMapMany(user -> {
// 只查询当前用户和第一层上级
if (user.getParentId() != null) {
return Flux.just(user, userService.getUserById(user.getParentId()).block());
} else {
return Flux.just(user);
}
});
userFlux.subscribe(user -> System.out.println("用户ID:" + user.getId() + ", 用户名:" + user.getName()));
}
}
该示例的输出只会到用户B,不会输出用户C,这就是两者的核心区别。
3. 背压处理
expand生成的Flux是支持背压的,底层的展开逻辑会遵循Reactor的背压机制,不会无限制地生成元素,因此在实际生产环境中可以放心使用,不用担心数据量过大导致的性能问题。
适用场景总结
Mono.expand最适合以下场景:
- 需要基于单个初始数据,逐层查询关联数据的场景,比如树形结构的向上或者向下遍历
- 关联查询的层级不确定,无法通过固定的多次查询完成的场景
- 需要以响应式流的方式返回所有关联数据,避免一次性加载全部数据导致内存压力的场景
只要掌握了expand的核心逻辑和终止条件的设置,就能很方便地用它构建出简洁高效的响应式链式查询逻辑,减少冗余的递归代码编写。
Mono.expand响应式编程ReactorFlux链式查询修改时间:2026-06-18 04:27:39