You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring WebFlux中重试时如何正确修改上下文?

问题原因分析

你的代码里withRetryContext没起到预期作用,核心原因有两个:

  1. 作用域局限:withRetryContext仅能修改Retry内部回调(比如doBeforeRetry)的上下文,不会影响整个调用链的上游逻辑,所以deferContextual拿不到这个"altered"值。
  2. 上下文传递顺序错误:你把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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 16:32:48