Caffeine自定义动态过期缓存未失效且无法刷新问题排查
问题分析与解决方案
核心问题拆解
1. Spring Cache注解与Caffeine原生API混用冲突
你同时使用了Spring的@Cacheable注解和Caffeine的oauth2Cache.get()方法,这是两个独立的缓存操作路径:
@Cacheable由Spring Cache框架接管缓存的读写、过期逻辑- 直接调用Caffeine的
get()会绕过Spring Cache的管理,导致自定义CustomExpiryOauth无法被正确触发,还会造成两个路径的缓存状态互相干扰
2. 自定义Expiry的未校验异常
expireAfterCreate中直接调用Long.parseLong(value.getExpiresIn())未做合法性校验,如果getExpiresIn()返回空、非数字字符串,会直接抛出NumberFormatException,这就是你添加新条目时报错的原因。
3. 缓存自动刷新逻辑错误
你预期缓存过期后自动刷新,但当前实现存在两个误区:
@Cacheable本身不会自动刷新缓存,仅在缓存不存在时调用方法加载数据- Caffeine的
CustomExpiry仅控制过期时间,自动刷新需要结合LoadingCache或专门的刷新机制
修正方案
方案1:统一使用Spring Cache管理(推荐)
完全依赖Spring Cache注解机制,避免与Caffeine原生API混用:
1. 修正Caffeine Bean配置
确保Bean名称与@Cacheable的cacheNames匹配(默认小写):
@Bean public Cache<String, Oauth2Token> oauth2Cache() { return Caffeine.newBuilder() .initialCapacity(100) .maximumSize(1000) .expireAfter(new CustomExpiryOauth()) .build(); }
2. 修复自定义Expiry的异常问题
添加参数合法性校验,避免数字解析异常:
public class CustomExpiryOauth implements CustomExpiry<String, Oauth2Token> { private static final Logger log = LoggerFactory.getLogger(CustomExpiryOauth.class); @Override public long expireAfterCreate(String key, Oauth2Token value, long currentTime) { String expiresInStr = value.getExpiresIn(); // 校验过期时间字符串的合法性 if (expiresInStr == null || !expiresInStr.matches("\\d+")) { log.error("Invalid expiresIn value: {}", expiresInStr); return TimeUnit.SECONDS.toNanos(300); // 默认5分钟过期作为降级 } long expiresIn = Long.parseLong(expiresInStr) - 2; log.info("expireAfterCreate value: {}", expiresIn); return TimeUnit.SECONDS.toNanos(expiresIn); } @Override public long expireAfterUpdate(String key, Oauth2Token value, long currentTime, long currentDuration) { return currentDuration; } @Override public long expireAfterRead(String key, Oauth2Token value, long currentTime, long currentDuration) { return currentDuration; } }
3. 修正服务类缓存方法
移除Caffeine原生调用,让@Cacheable完全接管缓存加载:
@Cacheable(cacheNames = "oauth2Cache", key = "#myMap.get('systemName')") public Oauth2Token getOauth2TokenFromCache(Map<String, String> myMap) { Oauth2Token token = getOauth2Token(myMap); log.info("At getOauth2TokenFromCache expiresIn = {} ", token.getExpiresIn()); return token; }
方案2:直接使用Caffeine LoadingCache实现自动刷新
如果需要更灵活的自动刷新逻辑,可以完全使用Caffeine的LoadingCache,不依赖Spring Cache注解:
1. 配置LoadingCache Bean
@Bean public LoadingCache<String, Oauth2Token> oauth2LoadingCache() { return Caffeine.newBuilder() .initialCapacity(100) .maximumSize(1000) .expireAfter(new CustomExpiryOauth()) // 可选:添加自动刷新策略(需结合业务调整刷新时机) // .refreshAfterWrite(5, TimeUnit.MINUTES) .build(key -> { // 根据key构造所需参数(示例:key为systemName) Map<String, String> myMap = new HashMap<>(); myMap.put("systemName", key); return getOauth2Token(myMap); }); }
2. 服务类中使用LoadingCache
@Autowired private LoadingCache<String, Oauth2Token> oauth2LoadingCache; public Oauth2Token getOauth2TokenFromCache(Map<String, String> myMap) { String systemName = myMap.get("systemName"); try { return oauth2LoadingCache.get(systemName); } catch (ExecutionException e) { log.error("Failed to load oauth2 token", e); throw new RuntimeException(e); } }
关键注意事项
- 禁止同时混用Spring Cache注解和Caffeine原生API,否则会导致缓存状态不一致、自定义过期逻辑失效
- 对
getExpiresIn()的返回值必须做合法性校验,避免解析异常 - 若需自动刷新缓存,优先使用Caffeine的
LoadingCache配合refreshAfterWrite,或结合Spring定时任务+@CacheEvict主动触发刷新
内容的提问来源于stack exchange,提问作者Arvind
相关产品推荐
相关产品推荐

