欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring @Cacheable的缓存数据手动清理问题 博客分类: spring springcache

程序员文章站 2024-03-17 22:08:10
...

Spring Cache 本身完美支持缓存的CRUD. 可以通过注解来实现缓存的清理, 详见: org.springframework.cache.annotation.CacheEvict. 此处不赘述. 

 

假如, 我们希望手动清理@Cacheable的缓存数据呢? 为什么有这样low的需求, 这么变态的需求? 运行中的系统总有千奇百怪的问题, 总有时候你可能需要手动清理缓存. 

而手动清理缓存多半不能找系统管理员, 系统管理员总有各种出于安全的理由拒绝为你效劳. 既然我们是程序员, 那就用程序的方式解决. 

 

分两步:

  1. 记录cacheName. cacheName就是@Cacheable注解的value. 
    之所以要记录cacheName, 是出于性能考虑. 一般产品线的Redis是不允许执行keys命令的. 所以不知道哪些数据被真正被缓存在了redis中. 
  2. 提供一个cache console页面, 用于访问redis并清理指定的cacheName对应的缓存数据. 这个页面是需要登录, 可以用公司的SSO登录, 并有一定的权限控制.

 

本文只涉及第一步. 第二步就是写一个web页面, 很简单, 所以就不提了.

 

记录cacheName要用到Spring的AOP.

 

/**
 * Aspect to handle Cache
 */
@Aspect
@Slf4j
class CacheAspect implements Ordered {
 
    @Pointcut("@annotation(org.springframework.cache.annotation.Cacheable)")
    public void cacheable() {
    }
 
    @Before("cacheable()")
    public void handleCacheable(JoinPoint joinPoint) throws Throwable {
        Method method = ObjectUtil.getAopTargetMethod(joinPoint);
        saveCacheName(method.getDeclaredAnnotation(Cacheable.class).cacheNames());
    }
 
    static void saveCacheName(String[] cacheNames) {
        Flux.fromArray(cacheNames)
                .filter(StringUtils::hasText)
                .doOnError(e -> log.warn("failed to saveCacheName: " + e.getMessage()))
                .subscribe(cacheName -> {
                    HashOperations<String, String, String> opsForHash = SpringContextHelper.started().getBean(StringRedisTemplate.class).opsForHash();
                    opsForHash.putIfAbsent("__cacheName:" + value(SpringContextHelper.started().getApplicationName(), "unknown"), cacheName, "{}");
                });
    }
 
    @Override
    public int getOrder() {
        return -999;
    }
}

这个AOP拦截所有对@Cacheable注解了的方法的访问, 记录其cacheName, 异步写入redis的一个hash中. 用于后续cache console页面的查询.

 

 

 

异步写入用到了Spring 5的Reactor.  

 

filter(StringUtils::hasText), 用于过滤空的cacheName, 有些开发人员可能粗心把@Cacheable的value指定为了一个空字符串, 此处要绕过空串避免AOP出错.

doOnError(e -> log.warn("failed to saveCacheName: " + e.getMessage())), 用于错误事件的处理.

subscribe(cacheName -> {...}); 这一句是实际写入redis. 

 

最后, 还需要注册到bean Factory. 

@Bean(name = "cacheAspect")
public CacheAspect cacheAspect() {
    return new CacheAspect();
}

 

 

 

 

相关标签: spring cache