背景
在使用SpringBoot提供的@CacheEvict注解时希望能够通过通配符批量删除一些keys;
//通过通配符删除keys
@CacheEvict(cacheNames = "cacheName", key = "keys:*")
上面的代码不能达到我们的目的,需要一点点改造;
@CacheEvict是如何使缓存失效的
SpringBoot提供的实现不支持按通配符进行删除;
这是SpringBoot的实现:
如何达到我们的目的
思路:
更改evict这个方法的实现,使其能够根据指定的pattern批量删除我们指定的key
怎么来实现
我们创建一个装饰器类来装饰这个默认的RedisCache类,做到比较优雅的实现我们的功能;
思路:代理CacheManager,在CacheManager中代理RedisCache
直接贴代码:
package cn.demo.demo; //你自己的包名
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.support.AbstractCacheManager;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* 这是一个CacheManager装饰器,本质上包装了RedisCache
* 使用装饰器模式扩展RedisCache的功能
* 扩展的功能有:
* *. evict方法支持删除以'*'结尾的通配符表示的所有的key(若key的字符串结尾为'*',则删除这个通配符匹配下的所有的key);
*/
public class RedisCacheManagerDecorator {
private final CacheManager baseManager;
private static final ConcurrentMap<Cache, Cache> cacheMap = new ConcurrentHashMap<>();
private static final List<MethodHandler> methodHandlers = new ArrayList<>();
static {
loadMethodHandlers();
}
private RedisCacheManagerDecorator(CacheManager baseManager) {
this.baseManager = baseManager;
}
/**
* 装饰CacheManager
* @param manager 要被装饰的CacheManager
* @return 装饰后的代理对象
*/
public static CacheManager decorate(CacheManager manager) {
RedisCacheManagerDecorator decorator = new RedisCacheManagerDecorator(manager);
return (CacheManager) Proxy.newProxyInstance(manager.getClass().getClassLoader(),
AbstractCacheManager.class.getInterfaces(),
cacheManagerInvocationHandler(decorator));
}
private static InvocationHandler cacheManagerInvocationHandler(RedisCacheManagerDecorator decorator) {
return (proxy, method, args) -> {
Object res = method.invoke(decorator.baseManager, args);
if (method.getName().equals("getCache")) {
res = decorateCache(res);
}
return res;
};
}
private static Object decorateCache(Object cacheObj) {
if (cacheObj instanceof Cache) {
Cache cache = (Cache) cacheObj;
Cache wrapperCache = cacheMap.get(cache);
if (wrapperCache == null) {
wrapperCache = buildRedisCacheProxy(cache);
cacheMap.putIfAbsent(cache, wrapperCache);
}
return wrapperCache;
}
return cacheObj;
}
/**
* 代理RedisCache
*/
private static Cache buildRedisCacheProxy(Cache cache) {
InvocationHandler handler = (proxy, method, args) -> {
MethodHandler methodHandler = methodHandlers.stream().filter(h -> h.canHandle(cache, method, args)).findFirst().orElse(null);
if (methodHandler != null) {
return methodHandler.handle(cache, method, args);
}
return method.invoke(cache, args);
};
return (Cache) Proxy.newProxyInstance(cache.getClass().getClassLoader(),
new Class<?>[]{Cache.class},
handler);
}
private static void loadMethodHandlers() {
methodHandlers.add(new EvictMethodHandler());
}
/**
* 代理evict方法
* 若key以'*'结尾,删除满足这个key的pattern的所有key
*/
static class EvictMethodHandler implements MethodHandler {
private static Method targetMethod;
static {
try {
targetMethod = Cache.class.getMethod("evict", Object.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
String parsePattern(Object[] args) {
if (args == null || args.length == 0) return null;
String value = String.valueOf(args[0]);
if (value.endsWith("*")) return value;
return null;
}
@Override
public boolean canHandle(Cache cache, Method method, Object[] args) {
return cache instanceof RedisCache
&& method.equals(targetMethod)
&& parsePattern(args) != null;
}
@Override
public Object handle(Cache cache, Method method, Object[] args) {
RedisCache redisCache = (RedisCache) cache;
String pattern = parsePattern(args);
RedisCacheConfiguration configuration = redisCache.getCacheConfiguration();
pattern = redisCache.getName() + "::" + pattern;
RedisCacheWriter cacheWriter = redisCache.getNativeCache();
byte[] convert = configuration.getConversionService().convert(pattern, byte[].class);
if (convert != null) {
cacheWriter.clean(redisCache.getName(), convert);
}
return null;
}
}
interface MethodHandler {
boolean canHandle(Cache cache, Method method, Object[] args);
Object handle(Cache cache, Method method, Object[] args);
}
}
如何使用:
在创建好RedisCacheManager后用上面的类创建一个装饰类:
上面的工作做完后就大功告成了;
原理: 当框架从@CacheEvict注解的信息拿到Cache实例时,框架拿到的是一个被包装的Cache实例(装饰器);这个装饰器拦截了Cache的evict方法,若装饰器发现evict方法的参数(即key)是以"*"结尾,拦截该方法并将与这个pattern匹配的所有的key都删除;