Spring WebFlux中重试时如何正确修改上下文?
问题原因分析
你的代码里withRetryContext没起到预期作用,核心原因有两个:
- 作用域局限:
withRetryContext仅能修改Retry内部回调(比如doBeforeRetry)的上下文,不会影响整个调用链的上游逻辑,所以deferContextual拿不到这个"altered"值。 - 上下文传递顺序错误:你把
contextWrite放在了retryWhen之后,每次重试重新订阅上游时,下游的contextWrite依然会把FLAG设为初始的"original"——Reactor上下文是从下游向上游传递的,上游的上下文由下游决定,重试时这个初始设置会覆盖任何重试相关的上下文修改。
解决方案
要让上游感知重试状态,必须在重试触发时修改上游订阅的上下文,以下两种方法都可以实现:
方案一:用RetrySpec的上下文钩子(Reactor 3.4+)
从Reactor 3.4版本开始,RetrySpec提供了withContextHook方法,能直接在重试时修改上游的上下文:
const val FLAG = "Flag" fun main() { val tr = TestRetry() tr.main() } class TestRetry { fun main() { Mono.deferContextual { ctx -> val flag = ctx.get<Any>(FLAG) println("flag ${flag}") Mono.just("hello") } .subscribeOn(Schedulers.boundedElastic()) .map { println("map: $it") throw IllegalArgumentException() } // 把初始上下文设置移到retryWhen之前,确保重试钩子能覆盖它 .contextWrite { println("CONTEXT WRITE initialising FLAG") it.put(FLAG, "original") } .retryWhen(retrySpec()) .subscribe() Thread.sleep(15000); } fun retrySpec(): RetrySpec = Retry.max(2L) .filter { it is IllegalArgumentException } .onRetryExhaustedThrow { _, _ -> RuntimeException("done") } // 重试时修改上游上下文 .withContextHook { ctx, _ -> ctx.put(FLAG, "altered") } .doBeforeRetry { val flag = it.retryContextView().getOrDefault(FLAG, "oops") println ("DO BEFORE RETRY Flag ${flag}") } }
方案二:函数式retryWhen手动控制上下文
如果你的Reactor版本较低,没有withContextHook,可以用函数式写法手动控制重试时的上下文:
const val FLAG = "Flag" fun main() { val tr = TestRetry() tr.main() } class TestRetry { fun main() { Mono.deferContextual { ctx -> val flag = ctx.get<Any>(FLAG) println("flag ${flag}") Mono.just("hello") } .subscribeOn(Schedulers.boundedElastic()) .map { println("map: $it") throw IllegalArgumentException() } .contextWrite { println("CONTEXT WRITE initialising FLAG") it.put(FLAG, "original") } .retryWhen { companion -> companion.flatMapIndexed { retryCount, retrySignal -> if (retryCount < 2 && retrySignal.failure() is IllegalArgumentException) { // 重试时返回带修改后上下文的Mono,传递给上游 Mono.just(Unit) .contextWrite { it.put(FLAG, "altered") } } else { Mono.error(RuntimeException("done")) } } } .subscribe() Thread.sleep(15000); } }
预期效果
修改后执行,你会看到:
- 第一次执行
deferContextual时,FLAG为"original" - 第一次及第二次重试时,
FLAG变为"altered" - 最终输出符合重试状态的上下文感知逻辑
内容的提问来源于stack exchange,提问作者TankerTom
相关产品推荐
相关产品推荐

