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

基于Cache-Control头实现Spring WebClient响应缓存的最优方案咨询

Spring WebClient 基于Cache-Control请求头的响应缓存实现咨询

我正在尝试实现基于Cache-Control(及相关)请求头的Spring WebClient响应缓存功能,清楚缓存与反应式编程并非天然适配,计划使用内存缓存。

我梳理了四种可行方案:

方案1:基于Project Reactor的Mono缓存方法

Project Reactor为Mono提供了多种cache方法:

return webClient.get()
        .uri("/hello")
        .retrieve()
        .bodyToMono(HelloWorld.class)
        .cache(Duration.of(3600, ChronoUnit.SECONDS))
        .block();

但这种方式是在ResponseSpec转换为实体Mono后缓存,会丢失所有请求头信息。可以改用toEntity来保留响应头:

return webClient.get()
        .uri("/hello")
        .retrieve()
        .toEntity(HelloWorld.class)
        .cache(Duration.of(3600, ChronoUnit.SECONDS))
        .block().getBody();

但我不确定能否在cache方法中直接访问ResponseEntity,比如下面的写法是否可行:

return webClient.get()
        .uri("/hello")
        .retrieve()
        .toEntity(HelloWorld.class)
        .flatMap(response ->
             Mono.just(response).cache(getTtlFromHeader(response.getHeaders()))
        )
        .block().getBody();

根据cache方法的JavaDoc:

  • ttlForValue:源产生有效值时调用的TTL生成函数,用于缓存响应
  • ttlForError:源产生错误时调用的TTL生成函数,这类情况不应缓存
  • ttlForEmpty:源为空时调用的TTL生成提供者,此时无内容可缓存

不过基于Mono的这类方案存在一个问题:无法重新评估缓存的响应是否仍然有效。

方案2:通过过滤器实现缓存

通过过滤器实现缓存功能,缓存对象是响应的Mono而非响应本身。我还没尝试过这种方式,但它显然不是最优解——Mono在首次调用后就已经解析完成,从缓存获取时仍需重复解析,而且缓存数据的有效性重新评估逻辑不清晰,实现起来会相当复杂。

方案3:WebClient调用外部处理缓存

由于我的所有调用都使用阻塞订阅者,外围逻辑是非反应式的,因此可以借助Spring缓存实现,但需要返回ResponseEntity而非仅响应对象。

我不太喜欢这个方案,因为ResponseEntity会脱离WebClient调用流程,我希望把所有和调用相关的逻辑都封装在WebClient内部。

方案4:基于Apache Async客户端的缓存实现

我目前使用Apache Async客户端作为底层连接器,可以将原有的客户端构建代码:

private CloseableHttpAsyncClient httpClient(int maxTotalConnections, int maxConnectionsPerRoute) {
    return HttpAsyncClients.custom()
            .setConnectionManager(connectionManager(maxTotalConnections, maxConnectionsPerRoute))
            .disableCookieManagement()
            .evictExpiredConnections()
            .disableRedirectHandling()
            .build();
}

替换为支持缓存的版本:

private CloseableHttpAsyncClient cachableHttpClient(int maxTotalConnections, int maxConnectionsPerRoute) {
    CacheConfig cacheConfig = CacheConfig.DEFAULT;
    return CachingHttpAsyncClients.custom()
            .setCacheConfig(cacheConfig)
            .setConnectionManager(connectionManager(maxTotalConnections, maxConnectionsPerRoute))
            .disableCookieManagement()
            .evictExpiredConnections()
            .disableRedirectHandling()    
            .build();
}

可能还需要进一步配置CacheConfig。这个方案看起来是最佳选择,但通过自动化测试验证其有效性的难度较大。

现在我有两个问题:

  • 是否还有我忽略的其他可行方案?
  • 哪种是最优的实现方式?

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 02:05:58