独立使用
在 Project Reactor 中,onErrorResume 和 onErrorContinue 都是处理流中异常的常用方法,但它们在流的生命周期控制 和应用场景上有本质区别:
- onErrorResume (降级/备选方案)
当流中发生错误时,onErrorResume 会捕获异常并切换 到一个新的 Publisher(备选流),原有的流会在此处终止。
-
行为 :类似于 Java 中的
try-catch块中catch后的return fallbackValue。 -
流的状态:上游流发生错误后不再发送后续元素,转而由你提供的备选流发送数据并正常结束。
-
示例代码 :
Flux.just(1, 2, 0, 4) .map(i -> 10 / i) // 当 i 为 0 时触发异常 .onErrorResume(e -> Flux.just(-1, -2)) // 发生错误后,原有流结束,开始发送 -1, -2 .subscribe(System.out::println); // 输出:10, 5, -1, -2
- onErrorContinue (跳过/继续执行)
onErrorContinue 允许你在发生错误时丢弃 导致错误的元素,并继续处理流中的后续元素。
-
行为 :类似于在
for循环中使用try-catch并执行continue。 -
流的状态 :流不会终止。它会针对当前错误元素执行回调逻辑(如打日志),然后请求上游发送下一个元素。
-
示例代码 :
Flux.just(1, 2, 0, 4) .map(i -> 10 / i) .onErrorContinue((e, val) -> System.err.println("Error on " + val)) // 仅跳过错误元素 .subscribe(System.out::println); // 输出:10, 5, (打印错误日志), 2.5
核心对比总结
| 特性 | onErrorResume | onErrorContinue |
|---|---|---|
| 主要目的 | 错误恢复/降级(Fallback) | 容错处理/跳过(Skip) |
| 流的命运 | 终止原有的流,切换到新流 | 维持原有的流,处理后续元素 |
| 适用场景 | 数据库查询失败返回缓存数据 | 批量处理中某一条记录损坏,不影响其他记录 |
| 层级影响 | 仅影响当前位置之后的链式调用 | 具有"上溯"特性,会影响上游的操作算子 |
⚠️ 使用建议
-
谨慎使用
onErrorContinue:它会改变上游算子的默认行为(让本该终止的流强行继续),有时会导致难以排查的副作用或资源泄漏。 -
局部处理优先 :如果你只想在
flatMap内部某次调用出错时不影响整个流,建议在flatMap内部使用onErrorResume返回Mono.empty(),这比全局使用onErrorContinue更安全、更符合响应式语义。
onErrorResume() 然后 onErrorContinue()
public static void main(String... args) {
Flux.range(1,5)
.doOnNext(i -> System.out.println("input=" + i))
.map(i -> i == 2 ? i / 0 : i)
.map(i -> i * 2)
.onErrorResume(err -> {
log.info("onErrorResume");
return Flux.empty();
})
.onErrorContinue((err, i) -> {log.info("onErrorContinue={}", i);})
.reduce((i,j) -> i+j)
.doOnNext(i -> System.out.println("sum=" + i))
.block();
}
输出如下:
input=1
input=2
17:47:05.789 [main] INFO com.example.demo.config.TestRunner - onErrorContinue=2
input=3
input=4
input=5
sum=26
这样的结果,你想到了吗?onErrorContinue() 会在 onErrorResume() 得到错误之前处理这个错误。当两个错误处理函数在同一个函数中的时候很明显,但是当你的函数中只有 onErrorResume(),而一些调用者实际上有 onErrorContinue() 时,你的 onErrorResume() 没有被调用的原因可能就不那么明显了。
使用 onErrorResume() 模拟 onErrorContinue()
有些博客建议我们完全不用 onErrorContinue(),且在所有场景中仅用 onErrorResume()。但是上述示例已经展示了它们会产生不同的结果。那我们怎么实现呢?
public static void main(String... args) {
Flux.range(1,5)
.doOnNext(i -> System.out.println("input=" + i))
.flatMap(i -> Mono.just(i)
.map(j -> j == 2 ? j / 0 : j)
.map(j -> j * 2)
.onErrorResume(err -> {
System.out.println("onErrorResume");
return Mono.empty();
})
)
.reduce((i,j) -> i+j)
.doOnNext(i -> System.out.println("sum=" + i))
.block();
}
因此,本质上是将可能在 flatMap 或 concatMap 中抛出错误的操作包装起来,并在其上使用 onErrorResume()。这样,它会产生相同的结果:
input=1
input=2
onErrorResume
input=3
input=4
input=5
sum=26
使用 onErrorResume() 和下游的 onErrorContinue() 模拟 onErrorContinue()
有时候,onErrorContinue() 放在调用程序中,您无法控制它。但你仍然需要 onErrorResume()。你该怎么办?
public static void main(String... args) {
Flux.range(1,5)
.doOnNext(i -> System.out.println("input=" + i))
.flatMap(i -> Mono.just(i)
.map(j -> j == 2 ? j / 0 : j)
.map(j -> j * 2)
.onErrorResume(err -> {
System.out.println("onErrorResume");
return Mono.empty();
})
.onErrorStop()
)
.onErrorContinue((err, i) -> {log.info("onErrorContinue={}", i);})
.reduce((i,j) -> i+j)
.doOnNext(i -> System.out.println("sum=" + i))
.block();
}
秘诀是在 onErrorResume() 代码块的末尾添加 onErrorStop() ------这会阻塞 onErrorContinue(),这样它就不会在 onErrorResume() 之前占用错误。尝试删除 onErrorStop(),你会看到 onErrorContinue() 在 onErrorResume 之前弹出。
输出为:
input=1
input=2
onErrorResume
input=3
input=4
input=5
sum=26
