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

如何在ProjectReactor中复用缓存的Auth Token?

问题分析

你的代码出现无限挂起的核心原因是循环订阅依赖:当调用login()时,你将authToken重新赋值为一个新的Mono,而这个Mono在不需要刷新的分支里直接返回当前的authToken(也就是正在构建的这个Mono本身),同时doesTokenNeedRefreshing()又依赖authToken的订阅,形成了无限循环的订阅链,最终导致程序挂起。

解决方案

可以通过原子性维护Token实例、消除循环订阅的方式修复,同时保证多线程环境下的线程安全,以下是两种可行的实现方案:

方案一:手动控制Token刷新逻辑

@Component
class ApiWebClient {
    private val webClient = WebClient.create()
    private val authTokenRef = AtomicReference<Mono<AuthToken>>(Mono.just(AuthToken("", Instant.ofEpochSecond(0))))

    fun login(): Mono<AuthToken> {
        return authTokenRef.updateAndGet { currentTokenMono ->
            currentTokenMono.flatMap { token ->
                if (doesTokenNeedRefreshing(token)) {
                    // 触发登录请求并缓存新Token
                    webClient.post()
                        .uri("https://example.com/login")
                        .body(Mono.just("Credentials"), String::class.java)
                        .exchangeToMono { it.bodyToMono<LoginResponse>() }
                        .map { response ->
                            LOGGER.info("Successfully logged in")
                            AuthToken(response.token, Instant.now())
                        }
                        .cache()
                } else {
                    // 复用现有Token
                    LOGGER.info("Reuse token")
                    currentTokenMono
                }
            }
        }
    }

    private fun doesTokenNeedRefreshing(token: AuthToken): Boolean {
        // 直接对Token实例做过期判断,避免订阅Mono引发循环
        return Instant.now().minusMillis(ONE_MINUTE_IN_MILLIS).isAfter(token.lastModified)
    }

    data class AuthToken(
        val token: String,
        val lastModified: Instant
    )

    companion object {
        private const val ONE_MINUTE_IN_MILLIS = 60 * 1000L
        private val LOGGER = LoggerFactory.getLogger(ApiWebClient::class.java)
    }
}

关键改进点

  • 消除循环订阅:直接对已获取的Token实例做过期判断,不再依赖authToken的订阅链
  • 线程安全:用AtomicReference维护Token的Mono实例,避免多线程下的竞态条件
  • 资源复用:WebClient实例化移至类成员,避免每次登录创建新实例

方案二:利用缓存自动过期简化逻辑

如果Token的过期规则固定,可以直接使用Mono.cache(Duration)让缓存自动过期,无需手动判断:

@Component
class ApiWebClient {
    private val webClient = WebClient.create()
    // 缓存1分钟,过期后自动触发新的登录请求
    private val authTokenMono = loginInternal().cache(Duration.ofMinutes(1))

    fun login(): Mono<AuthToken> {
        return authTokenMono
    }

    private fun loginInternal(): Mono<AuthToken> {
        return webClient.post()
            .uri("https://example.com/login")
            .body(Mono.just("Credentials"), String::class.java)
            .exchangeToMono { it.bodyToMono<LoginResponse>() }
            .map { response ->
                LOGGER.info("Successfully logged in")
                AuthToken(response.token, Instant.now())
            }
    }

    data class AuthToken(
        val token: String,
        val lastModified: Instant
    )

    companion object {
        private val LOGGER = LoggerFactory.getLogger(ApiWebClient::class.java)
    }
}

这种方式代码更简洁,由Reactor自动管理缓存的生命周期,缓存过期后再次调用login()会自动发起新的登录请求。

内容的提问来源于stack exchange,提问作者Archmede

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 00:45:12