Reactor中publish()与map操作符的核心差异及适用场景咨询
Reactor中publish()与map操作符的核心差异及适用场景
你在测试中感觉两者没区别,核心原因是测试场景太简单:既没有触发订阅(代码里没调用subscribe()),也没用到publish的核心能力——共享上游数据流、避免重复执行副作用。下面直接拆解两者的核心差异和适用场景:
1. 核心定位完全不同
map:单一元素转换操作符
map是Reactor最基础的元素转换工具,作用是接收流中的每个元素,同步执行转换逻辑后输出新元素,是严格的1对1元素映射。它不涉及流的共享,只关注单个元素的处理逻辑。
publish:上游流共享容器
publish的核心不是转换元素,而是让上游的Mono/Flux被多次订阅时,只触发一次上游的订阅逻辑。它接收一个函数,参数是上游的共享版流,你可以在这个函数内部对共享流做任意次数的订阅、转换、分支处理,所有操作都会复用同一次上游的执行结果,避免重复执行副作用(比如数据库查询、日志打印、IO操作等)。
2. 适用场景差异
用map的场景
- 简单的元素转换:比如字符串拼接、对象属性提取、基础类型转换(如
String转Integer) - 每个元素独立的同步处理逻辑,不需要复用上游的执行结果
- 示例:
// 把字符串转成大写 Mono<String> upperCaseMono = Mono.just("test") .map(String::toUpperCase);
用publish的场景
- 当你需要对同一个上游流做多次不同的处理/订阅,但不想重复触发上游的副作用
- 需要在一个逻辑块内复用上游流的结果,比如分多个分支处理后再合并
- 示例(对比map的差异):
// 上游带有副作用:模拟数据库查询 Mono<String> dbQueryMono = Mono.fromCallable(() -> { log.info("执行数据库查询"); return "db_result"; }); // 错误示范:直接多次订阅map后的流,会触发两次数据库查询 Mono<String> mapped = dbQueryMono.map(s -> s + "_processed"); mapped.subscribe(); mapped.subscribe(); // 日志会打印两次"执行数据库查询" // 正确方式:用publish共享上游,只触发一次数据库查询 Mono<Void> published = dbQueryMono.publish(sharedMono -> { // 分支1:转换后输出 Mono<String> branch1 = sharedMono.map(s -> s + "_branch1"); // 分支2:转换后存入缓存 Mono<Void> branch2 = sharedMono.flatMap(s -> cacheService.put("key", s)); // 等待两个分支完成 return Mono.when(branch1.subscribe(), branch2); }); published.subscribe(); // 日志只会打印一次"执行数据库查询"
为什么你的测试没看出差异?
你的测试代码中,两个Mono都没有调用subscribe(),所以上游的doOnNext逻辑根本没执行。即使加上subscribe(),第一个例子里publish内部只做了一次map,和直接调用map的效果一致——因为没有多次订阅上游的场景,publish的共享能力没发挥出来。
内容的提问来源于stack exchange,提问作者Peng
相关产品推荐
相关产品推荐

