稀土掘金 稀土掘金

SpringBoot @CacheEvict按通配符删除(redis)

背景

在使用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都删除;

4617作文网易烊千玺 周边周易取名的句子周易八字起名大全下载周易八字免费测算名字小儿八字起名大全龙凤双胞胎起什么名字好听算命先生下载周易测名字打分 五格姓名测试六画姓氏起名组合起名大全的软件八字周公解梦周易老黄历大全下载马姓明字辈起名八字命格免费测算周易姓名测姻缘起名酒店公司名字梦见被猫咬怎么化解周公解梦梦见死去的亲人周易起名公司大全第二个男孩起名刀剑如梦古筝讲解视频周易起名算命网周公解梦梦见秤北海鲸梦解析女孩起名带笑字的名字梦想城镇最新破解版起名姓刘男孩周公解梦 舟起英文网名的网站起名女孩姓周淀粉肠小王子日销售额涨超10倍罗斯否认插足凯特王妃婚姻让美丽中国“从细节出发”清明节放假3天调休1天男子给前妻转账 现任妻子起诉要回网友建议重庆地铁不准乘客携带菜筐月嫂回应掌掴婴儿是在赶虫子重庆警方辟谣“男子杀人焚尸”国产伟哥去年销售近13亿新的一天从800个哈欠开始男孩疑遭霸凌 家长讨说法被踢出群高中生被打伤下体休学 邯郸通报男子持台球杆殴打2名女店员被抓19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警两大学生合买彩票中奖一人不认账德国打算提及普京时仅用姓名山西省委原副书记商黎光被逮捕武汉大学樱花即将进入盛花期今日春分张家界的山上“长”满了韩国人?特朗普谈“凯特王妃P图照”王树国3次鞠躬告别西交大师生白宫:哈马斯三号人物被杀代拍被何赛飞拿着魔杖追着打315晚会后胖东来又人满为患了房客欠租失踪 房东直发愁倪萍分享减重40斤方法“重生之我在北大当嫡校长”槽头肉企业被曝光前生意红火手机成瘾是影响睡眠质量重要因素考生莫言也上北大硕士复试名单了妈妈回应孩子在校撞护栏坠楼网友洛杉矶偶遇贾玲呼北高速交通事故已致14人死亡西双版纳热带植物园回应蜉蝣大爆发男孩8年未见母亲被告知被遗忘张立群任西安交通大学校长恒大被罚41.75亿到底怎么缴沈阳一轿车冲入人行道致3死2伤奥运男篮美国塞尔维亚同组周杰伦一审败诉网易国标起草人:淀粉肠是低配版火腿肠外国人感慨凌晨的中国很安全男子被流浪猫绊倒 投喂者赔24万杨倩无缘巴黎奥运男子被猫抓伤后确诊“猫抓病”春分“立蛋”成功率更高?记者:伊万改变了国足氛围奥巴马现身唐宁街 黑色着装引猜测

4617作文网 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化