Spring WebFlux中Mono未等待执行完成就向下游传递空响应问题
问题根因与修复方案
核心问题
执行逻辑不符合预期的本质是Mono.justOrEmpty()使用错误,混淆了响应式流的装配阶段和执行阶段:
Mono.justOrEmpty()接受的是已经完成求值的常量值,你把数据库查询方法的返回值直接作为参数传入时,数据库查询会在调用find()方法、组装响应式流的瞬间就同步执行,根本不会等流被下游订阅时再触发。- 这个执行时机下请求上下文、事务、数据库连接资源往往还未就绪,查询大概率直接返回null,等后续流真正开始执行时,不会再重新触发数据库查询,直接把提前拿到的空结果往下游传递,表现为“没有等待查询完成就向下流转”。
- 如果你用的是本身返回
Mono的响应式Repository(如R2DBC实现),Mono.justOrEmpty()会把返回的Mono对象本身当成普通值包装,形成Mono<Mono<FulfilmentAcknowledgement>>的嵌套结构,内层的查询Mono根本不会被订阅,自然拿不到结果。
修复方案
根据使用的Repository类型选择对应实现即可:
场景1:使用同步阻塞型Repository(传统JDBC/MyBatis/非响应式JPA)
用Mono.fromCallable()包装阻塞查询逻辑,确保查询在流被订阅时才执行,同时显式将阻塞任务调度到专用的阻塞线程池,避免卡死Netty事件循环:
@org.jetbrains.annotations.NotNull public Mono<FulfilmentAcknowledgement> findFulfilmentAcknowledgement(String entityId, String itemId, Long fulfilmentId, String type) { return Mono.fromCallable(() -> fulfilmentAcknowledgementRepository.findFulfilmentAcknowledgement(entityId, itemId, fulfilmentId, type) ) // 阻塞任务必须调度到boundedElastic线程池,禁止占用事件循环线程 .subscribeOn(Schedulers.boundedElastic()); }
场景2:使用响应式Repository(R2DBC/ReactiveCrudRepository)
响应式Repository的查询方法本身就返回Mono<FulfilmentAcknowledgement>,不需要额外包装,直接返回即可:
@org.jetbrains.annotations.NotNull public Mono<FulfilmentAcknowledgement> findFulfilmentAcknowledgement(String entityId, String itemId, Long fulfilmentId, String type) { return fulfilmentAcknowledgementRepository.findFulfilmentAcknowledgement(entityId, itemId, fulfilmentId, type); }
额外优化建议
- 当前的
doOnError仅打印错误日志,错误会继续向下传播,不会触发switchIfEmpty的降级逻辑。如果希望查询报错时也走空值降级,可以补充错误恢复逻辑:
return fulfilmentAcknowledgementService.findFulfilmentAcknowledgement(entityId, itemId, fulfilmentId, type) .map(acknowledgement -> { validationService.validate(acknowledgement, CALLBACK); return RuntimeCallbackDetails.from(callback, acknowledgement, status); }) .doOnError(throwable -> log.error("Error while retrieving fulfilment acknowledgement for callback: ", throwable)) // 报错后返回空Mono,触发后续降级逻辑 .onErrorResume(e -> Mono.empty()) .switchIfEmpty(Mono.defer(() -> Mono.just(RuntimeCallbackDetails.from(callback, status))));
- 可以在数据库查询逻辑和
map操作中分别加日志验证执行时机:修复后查询日志会在流真正被处理时打印,和map中的日志顺序一致,不会在调用find()方法时就提前打印。
内容的提问来源于stack exchange,提问作者Gurucharan Sharma
相关产品推荐
相关产品推荐

