Java Spring@Cacheable:错误时保留旧值

Java Spring@Cacheable:错误时保留旧值,java,spring,caching,guava,ehcache,Java,Spring,Caching,Guava,Ehcache,我计划使用Spring@Cacheable注释来缓存调用方法的结果 但是这个实现在我看来并不十分“安全”。据我所知,返回的值将由底层缓存引擎缓存,并在调用Spring execute方法时删除 我需要一个在加载新值之前不会破坏旧值的实现。这是必需的,以下场景应该可以工作: 调用可缓存方法->返回有效结果 结果将由Spring@Cacheable后端缓存 Spring使缓存失效,因为它已过期(例如,TTL为1小时) 再次调用可缓存方法->返回异常/空值 旧结果将再次缓存,因此,该方法的未来调用将返

我计划使用Spring@Cacheable注释来缓存调用方法的结果

但是这个实现在我看来并不十分“安全”。据我所知,返回的值将由底层缓存引擎缓存,并在调用Spring execute方法时删除

我需要一个在加载新值之前不会破坏旧值的实现。这是必需的,以下场景应该可以工作:

  • 调用可缓存方法->返回有效结果
  • 结果将由Spring@Cacheable后端缓存
  • Spring使缓存失效,因为它已过期(例如,TTL为1小时)
  • 再次调用可缓存方法->返回异常/空值
  • 旧结果将再次缓存,因此,该方法的未来调用将返回有效结果

  • 这怎么可能呢?

    我对Spring代码的理解可能是错误的,尤其是
    org.springframework.cache.interceptor.CacheAspectSupport#执行(org.springframework.cache.interceptor.CacheAspectSupport.cacheOperationContext,org.springframework.cache.interceptor.CacheAspectSupport.cacheOperationContext)
    ,但我相信抽象并不能提供你真正需要的东西

  • Spring不会使条目过期,这将留给底层缓存实现
  • 您提到您希望看到值,即使它们已过期。这与我所知道的大多数缓存实现中使用的到期抽象背道而驰
  • 在调用错误时返回以前缓存的值显然是特定于用例的。Spring抽象将简单地将错误返回给用户。
    CacheErrorHandler
    机制只处理与缓存调用相关的异常

  • 总而言之,在我看来,您所要求的是非常特定于用例的,因此不是抽象会/应该提供的东西。

    如果
    @Cacheable
    方法抛出异常,您服务旧值的要求可以通过对Google Guava的最小扩展轻松实现

    使用以下配置示例

    @Configuration
    @EnableWebMvc
    @EnableCaching
    @ComponentScan("com.yonosoft.poc.cache")
    public class ApplicationConfig extends CachingConfigurerSupport {
        @Bean
        @Override
        public CacheManager cacheManager() {
            SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
    
            GuavaCache todoCache = new GuavaCache("todo", CacheBuilder.newBuilder()
                .refreshAfterWrite(10, TimeUnit.MINUTES)
                .maximumSize(10)
                .build(new CacheLoader<Object, Object>() {
                    @Override
                    public Object load(Object key) throws Exception {
                        CacheKey cacheKey = (CacheKey)key;
                        return cacheKey.method.invoke(cacheKey.target, cacheKey.params);
                    }
                }));
    
            simpleCacheManager.setCaches(Arrays.asList(todoCache));
    
            return simpleCacheManager;
        }
    
        @Bean
        @Override
        public KeyGenerator keyGenerator() {
            return new KeyGenerator() {
                @Override
                public Object generate(Object target, Method method, Object... params) {
                    return new CacheKey(target, method, params);
                }
            };
        }
    
        private class CacheKey extends SimpleKey {
            private static final long serialVersionUID = -1013132832917334168L;
            private Object target;
            private Method method;
            private Object[] params;
    
            private CacheKey(Object target, Method method, Object... params) {
                super(params);
                this.target = target;
                this.method = method;
                this.params = params;
            }
        }
    }
    
    @配置
    @EnableWebMvc
    @启用缓存
    @组件扫描(“com.yonosoft.poc.cache”)
    公共类应用程序配置扩展了CachingConfigurerSupport{
    @豆子
    @凌驾
    公共缓存管理器缓存管理器(){
    SimpleCacheManager SimpleCacheManager=新SimpleCacheManager();
    GuavaCache-todoCache=新的GuavaCache(“todo”,CacheBuilder.newBuilder()
    .refreshAfterWrite(10,时间单位:分钟)
    .最大尺寸(10)
    .build(新的缓存加载程序(){
    @凌驾
    公共对象加载(对象键)引发异常{
    CacheKey CacheKey=(CacheKey)key;
    返回cacheKey.method.invoke(cacheKey.target,cacheKey.params);
    }
    }));
    setCaches(Arrays.asList(todoCache));
    返回simpleCacheManager;
    }
    @豆子
    @凌驾
    公钥生成器KeyGenerator(){
    返回新的KeyGenerator(){
    @凌驾
    公共对象生成(对象目标、方法、对象…参数){
    返回新的CacheKey(目标、方法、参数);
    }
    };
    }
    私有类CacheKey扩展了SimpleKey{
    私有静态最终长SerialVersionId=-1013132832917334168L;
    私有对象目标;
    私有方法;
    私有对象[]参数;
    专用缓存密钥(对象目标、方法、对象…参数){
    超级(params);
    this.target=目标;
    这个方法=方法;
    this.params=params;
    }
    }
    }
    
    CacheKey
    仅用于公开
    SimpleKey
    属性。Guavas refreshAfterWrite将配置刷新时间,而不会使缓存项过期。如果用
    @Cacheable
    注释的方法抛出异常,则缓存将继续服务于旧值,直到由于
    maximumSize
    而被逐出,或者被成功方法响应中的新值替换。您可以将
    refreshAfterWrite
    expireAfterAccess
    expireAfterAccess
    结合使用