
在 Spring WebFlux 响应式应用中,避免阻塞锁(如 ReentrantLock)以维护非阻塞特性;推荐使用 Mono.cache(Duration) 实现线程安全、自动过期的令牌共享缓存。
在 spring webflux 响应式应用中,避免阻塞锁(如 `reentrantlock`)以维护非阻塞特性;推荐使用 `mono.cache(duration)` 实现线程安全、自动过期的令牌共享缓存。
在响应式编程模型中,同步阻塞操作是反模式——ReentrantLock 会阻塞当前线程,破坏 Reactor 的事件循环(Event Loop)调度机制,导致线程池耗尽、吞吐量下降,甚至引发死锁风险。你原代码中在 preAuthorize() 内直接加锁并调用 refreshToken()(一个异步 Mono),不仅逻辑上无法真正串行化并发请求(因 lock/unlock 在同步上下文执行,而 Mono 订阅发生在不同线程),更严重的是:authLock.lock() 阻塞了 reactor-http-nio 线程,违背了 WebFlux “无阻塞、高并发”的设计初衷。
✅ 正确解法:利用响应式原语实现无锁、共享、带 TTL 的令牌缓存
Spring Framework 5.3+ 提供了 Mono.cache(Duration) —— 它不是简单地缓存值,而是缓存整个 Mono 的订阅行为:首次订阅触发上游执行(如调用授权接口),后续并发订阅将共享该次执行结果,并在 TTL 过期后自动失效、触发下一次刷新。整个过程完全非阻塞、线程安全,且天然支持背压与错误传播。
以下为生产就绪的实现方式:
@Configuration
public class AuthConfig {
@Bean
public WebClient webClient(WebClient.Builder builder) {
return builder.build();
}
/**
* 声明式定义可缓存的令牌 Mono —— 全局单例,所有组件复用同一实例
*/
@Bean
public Mono<string> authHeader(WebClient webClient) {
return webClient.post()
.uri("https://api.example.com/oauth/token")
.bodyValue(Map.of("grant_type", "client_credentials"))
.retrieve()
.bodyToMono(JsonNode.class)
.map(node -> node.get("access_token").asText())
.cache(Duration.ofHours(1)); // ⚠️ TTL 必须 ≤ 服务端 token 有效期(如 1h)
}
}</string>
在业务 Service 中直接注入并使用:
@Service
@RequiredArgsConstructor
public class ApiService {
private final WebClient webClient;
private final Mono<string> authHeader; // 注入缓存的 Mono
public Mono<response> performRequest(Request request) {
return authHeader
.flatMap(token -> webClient.get()
.uri("https://api.example.com/data")
.header("Authorization", "Bearer " + token)
.retrieve()
.bodyToMono(Response.class));
}
}</response></string>
? 关键要点说明:
-
必须复用同一个
Mono实例:cache()仅对当前Mono对象生效。若每次调用都新建Mono.fromCallable(...).cache(),则每个实例独立缓存,失去共享意义。因此务必通过@Bean声明为 Spring 单例。 -
TTL 设置需谨慎:建议比服务端 token 过期时间提前 30–60 秒(如服务端 3600s,客户端设
Duration.ofSeconds(3540)),避免因网络延迟导致请求携带已过期 token。 -
错误处理增强(推荐):可在
cache()前链式添加.onErrorResume()或.retryBackoff(),提升容错性;如需刷新失败时返回旧值,可结合Mono.switchIfEmpty()与本地状态(但需确保状态更新也是响应式原子的)。 -
不适用场景:若 token 刷新需依赖动态上下文(如用户级密钥),则应改用
Mono.defer(() -> ...).cache(Duration)延迟求值,但仍保持单例 Bean 结构。
总结:响应式系统中的共享资源协调,核心思路是用声明式缓存替代命令式锁。Mono.cache(Duration) 以零成本实现线程安全、自动过期、懒加载与共享订阅,是 Spring WebFlux 生态中处理短生命周期凭证(token、配置、元数据)的最佳实践。










