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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 11:02:47