flatMap内抛出异常未被onErrorResume捕获问题及使用最佳实践咨询
问题解答
现象原因:是Reactor的设计使然
这个差异完全符合Reactor的设计逻辑,核心原因是两种错误的传递路径完全不同:
- 当
processEvent返回Mono.error时:错误是作为响应式流的标准onError信号发出的,会进入你在processEvent返回值后拼接的操作链,自然会被后续的onErrorResume捕获处理,处理后返回Mono.empty不会中断主Flux流,因此5个元素会依次处理。 - 当
processEvent直接抛出异常时:这个异常是Mapper函数执行过程中抛出的同步异常,此时还没有生成待订阅的Mono实例,后续拼接的doOnSuccess、onErrorResume根本没有机会执行。Reactor会直接将这类同步异常向上传递到外层流,触发外层的doOnError,并且默认会终止整个Flux流,因此后续元素不会被处理,handleError也不会被触发。
flatMap使用最佳实践
- 所有返回Publisher(Mono/Flux)的方法禁止直接抛出异常:这是响应式编程的基础约定,所有业务异常都必须包装为
Mono.error/Flux.error作为返回值传递,不能直接throw。 - 对不可控的第三方方法/遗留方法调用,如果不确定是否会直接抛出异常,需要在订阅前做好异常捕获封装。
- 错误处理操作符(如
onErrorResume、onErrorReturn)仅能捕获其所在操作链上游发出的onError信号,无法捕获操作符本身执行时抛出的同步异常。
更优的解决方案
不需要手动写try-catch,Reactor提供了原生工具自动捕获同步异常并包装为error信号:
方案1:使用Mono.defer包裹调用
将processEvent的调用放入Mono.defer的Supplier中,defer会自动捕获Supplier执行过程中抛出的所有同步异常,自动包装为Mono.error向下传递,确保后续的onErrorResume可以正常捕获:
.flatMap(event -> Mono.defer(() -> processEvent(event)) .doOnSuccess(result -> log.info("Processed - {}", event)) .onErrorResume(t -> handleError(t, event)) )
这种方案改造成本极低,且完全覆盖同步异常、异步error信号两种场景。
方案2:规范内部方法实现
如果processEvent是团队内部维护的方法,直接修改方法实现,确保所有异常都通过Mono.error返回,从根源上避免直接抛出异常的情况。
方案3:使用代码检查规则兜底
可以通过ArchUnit等代码检查工具,新增规则校验所有返回值为Mono/Flux的方法,不允许直接抛出异常,从CI层面避免遗漏。
内容的提问来源于stack exchange,提问作者J_D
相关产品推荐
相关产品推荐

