目錄
前言
眾所周知,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,就全緩存遍歷幾次。具體實現留給讀者自行完成。希望對各位有所幫助。