如何在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
相关产品推荐
相关产品推荐

