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

Spring WebClient如何结合缓存处理NOT_MODIFIED(304)响应?

解决方案与实现建议

核心思路

将缓存内容与ETag绑定存储,收到304响应时直接返回本地缓存;仅在远程资源实际变化时更新缓存,同时保留兜底过期时间避免缓存无限期留存。针对缓存过期但远程返回304的场景,需重新发起无ETag的请求获取内容并更新缓存。

1. 调整缓存结构

定义封装类同时存储响应体与ETag,用Caffeine实现缓存兜底:

// 缓存封装类,绑定响应体与ETag
public record CachedPage(PageObject page, String etag) {}

// 缓存管理类
@Component
public class PageCache {
    private final Cache<String, CachedPage> cache = Caffeine.newBuilder()
            .expireAfterWrite(24, TimeUnit.HOURS) // 兜底过期时间,避免ETag失效后无缓存可用
            .build();

    public CachedPage get(String uri) {
        return cache.getIfPresent(uri);
    }

    public void put(String uri, CachedPage cachedPage) {
        cache.put(uri, cachedPage);
    }
}

2. 重写WebClient请求逻辑

private final PageCache pageCache;
private final WebClient webClient;

// 构造注入依赖
public YourService(PageCache pageCache, WebClient webClient) {
    this.pageCache = pageCache;
    this.webClient = webClient;
}

public Mono<PageObject> getPageObject(final String callingUri) {
    // 先尝试从缓存获取内容与ETag
    CachedPage cachedPage = pageCache.get(callingUri);
    String etag = cachedPage != null ? cachedPage.etag() : null;

    return webClient.get()
            .uri(callingUri)
            .ifNoneMatch(etag)
            .exchangeToMono(clientResponse -> {
                if (clientResponse.statusCode().isSameCodeAs(HttpStatus.NOT_MODIFIED)) {
                    // 处理304响应
                    if (cachedPage != null) {
                        // 缓存存在,返回缓存内容并重置过期时间
                        pageCache.put(callingUri, cachedPage);
                        return Mono.just(cachedPage.page());
                    } else {
                        // 缓存已过期但ETag有效,重新发起无ETag请求获取内容
                        return webClient.get()
                                .uri(callingUri)
                                .exchangeToMono(newResp -> {
                                    if (newResp.statusCode().is2xxSuccessful()) {
                                        String newEtag = newResp.headers().header("etag").stream().findFirst().orElse(null);
                                        return newResp.bodyToMono(PageObject.class)
                                                .doOnNext(page -> {
                                                    if (newEtag != null) {
                                                        pageCache.put(callingUri, new CachedPage(page, newEtag));
                                                    }
                                                });
                                    }
                                    return newResp.createException().flatMap(Mono::error);
                                });
                    }
                } else if (clientResponse.statusCode().is2xxSuccessful()) {
                    // 处理200响应,更新缓存与ETag
                    String newEtag = clientResponse.headers().header("etag").stream().findFirst().orElse(null);
                    return clientResponse.bodyToMono(PageObject.class)
                            .doOnNext(page -> {
                                if (newEtag != null) {
                                    pageCache.put(callingUri, new CachedPage(page, newEtag));
                                }
                            });
                }
                // 处理其他错误状态
                return clientResponse.createException().flatMap(Mono::error);
            });
}

3. 关键逻辑说明

  • 304响应处理:收到304时优先返回本地缓存,通过重新存入缓存重置过期时间,避免缓存因兜底策略到期被清理。
  • 缓存过期+304场景:若缓存已被清理但ETag仍有效,需重新发起无ETag的请求获取内容(因为304无响应体),再更新缓存。
  • 阻塞模式适配:在MVC Controller中调用时使用block()获取同步结果:
@GetMapping("/show-page")
public String renderPage(Model model) {
    PageObject page = yourService.getPageObject("https://remote-api/target-page").block();
    model.addAttribute("page", page);
    return "page-template";
}

额外建议

  • ETag格式适配:若远程返回弱ETag(带W/前缀),需在ifNoneMatch中同步添加前缀,避免匹配失败。
  • 异常容错:添加onErrorResume逻辑,在远程请求失败时返回缓存内容(如果存在),提升系统稳定性。
  • 缓存键优化:若请求URL包含查询参数,需对参数排序后生成缓存键,避免因参数顺序不同导致缓存冗余。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 02:58:25