众所周知, encache 是现在最流行的 java 开源缓存框架, 配置简单, 结构清晰, 功能强大通过注解 @Cacheable 可以快速添加方法结果到缓存通过 @CacheEvict 可以快速清除掉指定的缓存
但由于 @CacheEvict 注解使用的是 key-value 的, 不支持模糊删除, 就会遇到问题当我用 @Cacheable 配合 Spring EL 表达式添加了同一方法的多个缓存比如:
@GetMapping("/listOfTask/{page}/")
@Cacheable(value = "BusinessCache", key = "'listOfTask_'+ #page")
public ResponseMessage<PageTaskVO> getTaskList(@PathVariable("page") String page) {
do something...
}
上述代码是分页获取任务信息用 EL 表达式获取到参数中的 page, 并作为缓存的 key, 使用 @Cacheable 添加到 ehcache 的缓存中此时, 在缓存中就会出现 listOfTask_1, listOfTask_2, listOfTask_3 这种类型的 key
当添加删除任务时, 列表就会发生改变这时候, 就需要把 listOfTask_* 相关的缓存全部去掉而这时, 我不知道缓存中到底缓存了多少和 listOfTask_* 相关的内容, 不可能调用 @CacheEvict 挨个删除
既然 ehcache 本身无法支持, 那就只能靠我们自己实现了
实现
考虑到使用的注解添加的缓存, 那么移除缓存也使用注解处理, 可以保持开发的一致性注解对开发者来说也很友好那么我们就考虑使用自定义注解来来模糊批量移除缓存
首先, 定义注解 CacheRemove:
@Target({
java.lang.annotation.ElementType.METHOD
})@Retention(RetentionPolicy.RUNTIME) public@interface CacheRemove {
String value();
String[] key();
}
其中, value 同 ehcache 一样, 用于定义要操作的缓存名 key 是一个数组, 用于存放多种缓存 key 的正则表达式起名 CacheRemove 清晰易懂, 也不与 ehcache 本身的注解冲突注解的定义到此为止接下来, 就需要处理注解了, 由于使用的 spring 框架, 很自然的, 就会想到用 AOP 来做注解的具体实现
注解的目的是批量模糊移除缓存需考虑如下两个问题:
用什么方式模糊匹配
怎么批量删除 key
我给出的处理方式, 也是我认为最简单的处理方式是:
用什么方式模糊匹配 CacheRemove 中的 key 传正则, 可以传多个, 使用正则匹配
怎么批量删除 key 循环所有的 key, 找到匹配正则的就删除
首先定义类名 CacheRemoveAspect:
@Aspect
@Component
public class CacheRemoveAspect {
@Pointcut(value = "(execution(* *.*(..)) && @annotation(com.example.CacheRemove))")
private void pointcut() {}
do something...
}
在切面中定义切点, 使用 execution(* *.*(..) && @annotation(com.example.CacheRemove)) 表示所有带注解类 CacheRemove 都执行,@annotation 中的值是注解的全限定名
切点定义完毕, 下面的重头戏就是切面的具体实现了一般来说, 缓存会在增删改的方法执行完后才要移除所以使用 @AfterReturning() 来实现在具体实现中需要做以下几件事:
拦截方法上的注解
判断注解是不是 CacheRemove
由于注解传入的 key 是个数组, 循环处理每个 key
在循环中编制每个 key 为 pattern, 并循环所有的缓存, 移除匹配上的缓存
具体实现如下:
@AfterReturning(value = "pointcut()") private void process(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
CacheRemove cacheRemove = method.getAnnotation(CacheRemove.class);
if (cacheRemove != null) {
String value = cacheRemove.value();
String[] keys = cacheRemove.key(); // 需要移除的正则 key
List cacheKeys = CacheUtils.cacheKeys(value);
for (String key: keys) {
Pattern pattern = Pattern.compile(key);
for (Object cacheKey: cacheKeys) {
String cacheKeyStr = String.valueOf(cacheKey);
if (pattern.matcher(cacheKeyStr).find()) {
CacheUtils.remove(value, cacheKeyStr);
}
}
}
}
}
以上, 为 ehcache 模糊批量移除缓存的具体实现其中 BusinessCacheUtils 为自己封装的 ehcache 工具类主要实现获取缓存池, 获取缓存, 移除缓存, 添加缓存, 查看所有缓存等正常功能代码如下:
public class CacheUtils {
private static CacheManager cacheManager = SpringContextHolder.getBean("ehCacheManagerFactory");
public static Object get(String cacheName, String key) {
Element element = getCache(cacheName).get(key);
return element == null ? null: element.getObjectValue();
}
public static void put(String cacheName, String key, Object value) {
Element element = new Element(key, value);
getCache(cacheName).put(element);
}
public static void remove(String cacheName, String key) {
getCache(cacheName).remove(key);
}
public static List cacheKeys(String cacheName) {
return getCache(cacheName).getKeys();
}
/**
* 获得一个 Cache, 没有则创建一个
* @param cacheName
* @return
*/
private static Cache getCache(String cacheName) {
Cache cache = cacheManager.getCache(cacheName);
if (cache == null) {
cacheManager.addCache(cacheName);
cache = cacheManager.getCache(cacheName);
cache.getCacheConfiguration().setEternal(true);
}
return cache;
}
public static CacheManager getCacheManager() {
return cacheManager;
}
}
至此, 整个 ehcache 模糊批量移除缓存的功能就实现了
总结
整个过程思路简单, 用到了一些 AOP 的知识就完成了需要的功能但具体的移除部分代码可考虑进行优化通过一次缓存的全部循环, 就把需要移除的缓存都移除干净, 而不是想现在这样有几个 key, 就全缓存遍历几次具体实现留给读者自行完成希望对各位有所帮助
来源: https://www.cnblogs.com/zer0Black/p/8410984.html