Spring @Cacheable如何忽略Feign异常返回上次有效缓存结果
问题根因
原有方案失效的核心逻辑偏差:
- Spring
@Cacheable的执行逻辑是固定的:缓存命中且未过期时直接返回缓存值,完全不执行目标方法;仅当缓存不存在、已过期时才会执行目标方法,执行完成后再根据unless/condition规则判断是否将返回值写入缓存。 - 现有缓存配置使用
expireAfterWrite(5, TimeUnit.MINUTES)写后过期策略,缓存到期后会直接删除旧条目,此时请求进入方法执行Feign调用,若调用失败返回null,缓存中已经没有旧值可供返回。unless = "#result == null"仅能阻止null被写入缓存,无法恢复已被删除的历史值。 - 该策略从设计上就无法满足「刷新失败回退旧值」的需求,回退的核心前提是旧值在刷新前不会被提前删除。
推荐方案(基于Spring缓存抽象+Caffeine,无额外技术栈引入)
使用Caffeine的refreshAfterWrite刷新策略替代expireAfterWrite过期策略,配合自定义CacheLoader实现异常回退逻辑,完全匹配需求:
refreshAfterWrite策略:缓存写入达到配置时长后不会删除旧值,第一次访问触发异步刷新,刷新过程中所有请求直接返回旧值;刷新成功则替换为新值,刷新失败则保留旧值,等待下一个刷新周期重试。- 自定义
CacheLoader拆分两种加载场景:首次无缓存时加载失败返回空列表,刷新时加载失败返回旧值。
代码实现
1. 修改缓存配置
@Configuration @EnableCaching class ClientConfig { @Bean @Qualifier("itemClientCacheManager") fun itemClientCacheManager(itemFeignClient: ItemFeignClient): CaffeineCacheManager { return object : CaffeineCacheManager("get_items") { override fun createCaffeineCache(cacheName: String): Cache<Any, Any> { val cacheLoader = object : CacheLoader<String, List<String>> { // 首次无缓存时的加载逻辑 override fun load(key: String): List<String> { return try { itemFeignClient.getItems() } catch (e: Exception) { // 首次加载失败返回空列表 emptyList() } } // 到期刷新时的加载逻辑 override fun reload(key: String, oldValue: List<String>): List<String> { return try { itemFeignClient.getItems() } catch (e: Exception) { // 刷新失败直接返回旧值,不更新缓存 oldValue } } } return Caffeine.newBuilder() .maximumSize(10) // 配置5分钟自动刷新,刷新期间保留旧值 .refreshAfterWrite(5, TimeUnit.MINUTES) // 可选配置:30分钟写后硬过期,避免旧值无限期留存 .expireAfterWrite(30, TimeUnit.MINUTES) .build(cacheLoader) as Cache<Any, Any> } } } }
2. 简化Service层代码
移除方法内手动捕获异常返回null的逻辑,指定固定缓存key(无参方法使用固定key即可):
@Service class ItemService( private val itemFeignClient: ItemFeignClient, ) { @Cacheable( cacheManager = "itemClientCacheManager", cacheNames = ["get_items"], key = "'all_items'" ) fun getItems(): List<String> { // 兜底逻辑,正常加载/刷新流程优先走CacheLoader return try { itemFeignClient.getItems() } catch (e: Exception) { emptyList() } } }
逻辑说明
- 首次调用:缓存无值,同步执行Feign调用,成功则缓存结果并返回,失败返回空列表。
- 缓存写入后5分钟内:所有请求直接返回缓存值,不触发Feign调用,与原有正常缓存逻辑一致。
- 写入满5分钟后:第一个请求触发异步刷新,立即返回旧值;刷新成功则替换为新值,后续请求返回新结果;刷新失败则保留旧值,后续请求继续返回旧值,直到下一个5分钟周期再次尝试拉取。
- 若配置硬过期时间,达到时间后缓存会被删除,逻辑回到首次调用的处理流程。
可选替代方案(Feign层面实现)
如果不便调整现有缓存配置,可通过Feign Fallback机制实现异常回退:
- 为
ItemFeignClient自定义Fallback实现类,Feign调用抛出异常时自动进入Fallback逻辑。 - 在Fallback类中注入
itemClientCacheManager,手动读取get_items缓存中已存在的值,有值则直接返回,无值则返回空列表。
该方案需要手动操作缓存API,逻辑耦合度更高,仅推荐在缓存配置无法调整时使用。
内容的提问来源于stack exchange,提问作者aSemy
相关产品推荐
相关产品推荐

