Spring @Cacheable的缓存数据手动清理问题 博客分类: spring springcache
程序员文章站
2024-03-17 22:08:10
...
Spring Cache 本身完美支持缓存的CRUD. 可以通过注解来实现缓存的清理, 详见: org.springframework.cache.annotation.CacheEvict. 此处不赘述.
假如, 我们希望手动清理@Cacheable的缓存数据呢? 为什么有这样low的需求, 这么变态的需求? 运行中的系统总有千奇百怪的问题, 总有时候你可能需要手动清理缓存.
而手动清理缓存多半不能找系统管理员, 系统管理员总有各种出于安全的理由拒绝为你效劳. 既然我们是程序员, 那就用程序的方式解决.
分两步:
- 记录cacheName. cacheName就是@Cacheable注解的value.
之所以要记录cacheName, 是出于性能考虑. 一般产品线的Redis是不允许执行keys命令的. 所以不知道哪些数据被真正被缓存在了redis中. - 提供一个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(); }