增强Spring @CacheEvict实现key模糊匹配清除

增强Spring @CacheEvict实现key模糊匹配清除系统中集成了 Springcache 使用 CacheEvict 进行缓存清除 CacheEvict 可以清除指定的 key 同时可以指定 allEntries true 清空 namespace 下的所有元素 现在遇到一个问题使用 allEntries true 清空 namespace 的值只能是常量 但是我现在需要将缓存根据租户的唯一 TelnetID 进行分离 这就导致 allEntries true 不能使用了 否则一旦触发清除缓存 将会导致全部的缓存清空 而我只想清空当前租户的缓存 熟悉 re

          系统中集成了Spring cache 使用@CacheEvict进行缓存清除,@CacheEvict可以清除指定的key,同时可以指定allEntries = true清空namespace下的所有元素,现在遇到一个问题使用allEntries = true清空namespace的值只能是常量,但是我现在需要将缓存根据租户的唯一TelnetID进行分离,这就导致allEntries = true不能使用了,否则一旦触发清除缓存,将会导致全部的缓存清空,而我只想清空当前租户的缓存,熟悉redis命令的人都知道,查询和删除都可以做模糊匹配,所以我就想让SpringCache的@CacheEvict也支持模糊匹配清除。

         先去搞清楚@CacheEvict是怎么实现缓存清理的,因为之前看过redis的源码,知道@CacheEvict是通过AOP实现的,其中核心的类是CacheAspectSupport,具体的源码分析细节大家可以自行Google,我只简单的分析一下CacheAspectSupport这个类里面重点的几个方法

private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) { // Special handling of synchronized invocation if (contexts.isSynchronized()) { CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next(); if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) { Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT); Cache cache = context.getCaches().iterator().next(); try { return wrapCacheValue(method, cache.get(key, () -> unwrapReturnValue(invokeOperation(invoker)))); } catch (Cache.ValueRetrievalException ex) { // The invoker wraps any Throwable in a ThrowableWrapper instance so we // can just make sure that one bubbles up the stack. throw (CacheOperationInvoker.ThrowableWrapper) ex.getCause(); } } else { // No caching required, only call the underlying method return invokeOperation(invoker); } } // Process any early evictions processCacheEvicts(contexts.get(CacheEvictOperation.class), true, CacheOperationExpressionEvaluator.NO_RESULT); // Check if we have a cached item matching the conditions Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class)); // Collect puts from any @Cacheable miss, if no cached item is found List<CachePutRequest> cachePutRequests = new LinkedList<>(); if (cacheHit == null) { collectPutRequests(contexts.get(CacheableOperation.class), CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests); } Object cacheValue; Object returnValue; if (cacheHit != null && !hasCachePut(contexts)) { // If there are no put requests, just use the cache hit cacheValue = cacheHit.get(); returnValue = wrapCacheValue(method, cacheValue); } else { // Invoke the method if we don't have a cache hit returnValue = invokeOperation(invoker); cacheValue = unwrapReturnValue(returnValue); } // Collect any explicit @CachePuts collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests); // Process any collected put requests, either from @CachePut or a @Cacheable miss for (CachePutRequest cachePutRequest : cachePutRequests) { cachePutRequest.apply(cacheValue); } // Process any late evictions processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue); return returnValue; }

这个方法是缓存控制入口核心方法processCacheEvicts会根据CacheOperationContext进行缓存清理处理,我们可以看到调用的一个performCacheEvict方法

private void performCacheEvict( CacheOperationContext context, CacheEvictOperation operation, @Nullable Object result) { Object key = null; for (Cache cache : context.getCaches()) { if (operation.isCacheWide()) { // 如果allEntries为ture就执行这个逻辑 logInvalidating(context, operation, null); doClear(cache); } else {// 否则执行删除指定的key if (key == null) { key = generateKey(context, result); } logInvalidating(context, operation, key); doEvict(cache, key); } } }

看到这里,我们就知道怎么回事了,继续debug跟进去,看到doClear和doEvict最终分别会调用RedisCache中的evict和clear方法

@Override public void evict(Object key) { cacheWriter.remove(name, createAndConvertCacheKey(key)); } /* * (non-Javadoc) * @see org.springframework.cache.Cache#clear() */ @Override public void clear() { // 支持模糊删除 byte[] pattern = conversionService.convert(createCacheKey("*"), byte[].class); cacheWriter.clean(name, pattern); }

我们看到如果allEntries为ture的时候最终执行的是clear()这个方法,其实他也是模糊删除的,只是他的key规则是namespace:: *,看到这里就看到希望了我们只需要想办法在namespace *中插入我们的telnetID就可以变成namespace ::telnetID:*这种格式,也就达到了我们的目的了。

   重点需要重写RedisCache的evict方法,新建一个RedisCacheResolver集成RedisCache,重写evict方法

public class RedisCacheResolver extends RedisCache { private final String name; private final RedisCacheWriter cacheWriter; private final ConversionService conversionService; protected RedisCacheResolver(String name, RedisCacheWriter cacheWriter, RedisCacheConfiguration cacheConfig) { super(name, cacheWriter, cacheConfig); this.name = name; this.cacheWriter = cacheWriter; this.conversionService = cacheConfig.getConversionService(); } / * * @Title: evict * @Description: 重写删除的方法 * @param @param key * @throws * */ @Override public void evict(Object key) { // 如果key中包含"noCacheable:"关键字的,就不进行缓存处理 if (key.toString().contains(RedisConstant.NO_CACHEABLE)) { return; } if (key instanceof String) { String keyString = key.toString(); // 后缀删除 if (StringUtils.endsWith(keyString, "*")) { evictLikeSuffix(keyString); return; } } // 删除指定的key super.evict(key); } / * 后缀匹配匹配 * * @param key */ private void evictLikeSuffix(String key) { byte[] pattern = this.conversionService.convert(this.createCacheKey(key), byte[].class); this.cacheWriter.clean(this.name, pattern); } }

     现在我们需要让我们的这个RedisCacheResolver 生效,所以需要将我们的RedisCacheResolver 注入到RedisCacheManager中,因此我们需要定义一个我们自己的RedisCacheManagerResolver集成RedisCacheManager

public class RedisCacheManagerResolver extends RedisCacheManager { private final RedisCacheWriter cacheWriter; private final RedisCacheConfiguration defaultCacheConfig; public RedisCacheManagerResolver(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) { super(cacheWriter, defaultCacheConfiguration); this.cacheWriter = cacheWriter; this.defaultCacheConfig = defaultCacheConfiguration; } public RedisCacheManagerResolver(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, String... initialCacheNames) { super(cacheWriter, defaultCacheConfiguration, initialCacheNames); this.cacheWriter = cacheWriter; this.defaultCacheConfig = defaultCacheConfiguration; } public RedisCacheManagerResolver(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, boolean allowInFlightCacheCreation, String... initialCacheNames) { super(cacheWriter, defaultCacheConfiguration, allowInFlightCacheCreation, initialCacheNames); this.cacheWriter = cacheWriter; this.defaultCacheConfig = defaultCacheConfiguration; } public RedisCacheManagerResolver(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations) { super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations); this.cacheWriter = cacheWriter; this.defaultCacheConfig = defaultCacheConfiguration; } public RedisCacheManagerResolver(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations, boolean allowInFlightCacheCreation) { super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, allowInFlightCacheCreation); this.cacheWriter = cacheWriter; this.defaultCacheConfig = defaultCacheConfiguration; } public RedisCacheManagerResolver(RedisConnectionFactory redisConnectionFactory, RedisCacheConfiguration cacheConfiguration) { this(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),cacheConfiguration); } / * 覆盖父类创建RedisCache,采用自定义的RedisCacheResolver * @Title: createRedisCache * @Description: TODO * @param @param name * @param @param cacheConfig * @param @return * @throws * */ @Override protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) { return new RedisCacheResolver(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig); } @Override public Map<String, RedisCacheConfiguration> getCacheConfigurations() { Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>(getCacheNames().size()); getCacheNames().forEach(it -> { RedisCache cache = RedisCacheResolver.class.cast(lookupCache(it)); configurationMap.put(it, cache != null ? cache.getCacheConfiguration() : null); }); return Collections.unmodifiableMap(configurationMap); } }

        至此我们就完成了关键的步骤,最后只需要RedisConfig中管理我们自己的RedisCacheManagerResolver即可

 public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { // 设置全局过期时间,单位为秒 Duration timeToLive = Duration.ZERO; timeToLive = Duration.ofSeconds(timeOut); // RedisCacheManager cacheManager = new RedisCacheManager( // RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory), // this.getRedisCacheConfigurationWithTtl(timeToLive), // 默认策略,未配置的 value 会使用这个 // this.getRedisCacheConfigurationMap() // 指定 value策略 // ); RedisCacheManagerResolver cacheManager = new RedisCacheManagerResolver(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory), this.getRedisCacheConfigurationWithTtl(timeToLive), // 默认策略,未配置的 value 会使用这个 this.getRedisCacheConfigurationMap() // 指定 value策略 ); // RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config).build(); return cacheManager; }

      经过上面的步骤,我们已经实现了重写evict方法,用来模糊删除缓存了

@CacheEvict(value = "BASE_CACHE, key = "#modelClassName + #telenetId+ '*'", allEntries = false)

只需要用上面这个注解,我们就可以删除telnetID下所有的缓存了

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/230244.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月9日 下午7:01
下一篇 2026年3月9日 下午7:22


相关推荐

  • ssm框架过时了吗_spring源码

    ssm框架过时了吗_spring源码SpringSpring是一个开源的免费的框架Spring是一个轻量级的,非入侵式的框架控制反转(IOC),面向切面编程(AOP)支持事务的处理,对框架整合的支持IOC理论UserDaoUserDaoImpUserSeviceUserServiceImp在之前,用户的需求可能会影响原来的代码。使用一个set。public void setUserDao(UserDao userDao){ this.userDao = userDao;}之前是主动创建对象,控制

    2022年8月8日
    9
  • python做cae库_python常用模块-OS模块

    python做cae库_python常用模块-OS模块importos__file__:指当前文件,带有路径的D:/svn_auto3/test_case1/test1.py(注意这里的斜杠,和abspath的区别就是这里)#路径操作>>>os.chdir(‘D:\\’)#进入目录#目录切换操作>>>importos>>>os.curdir’.’>>>os.pardir’…

    2022年5月2日
    38
  • swoole源码安装步骤

    swoole源码安装步骤

    2022年2月12日
    43
  • 互联网医疗行业PEST分析实践「建议收藏」

    互联网医疗行业PEST分析实践「建议收藏」Python微信订餐小程序课程视频https://edu.csdn.net/course/detail/36074Python实战量化交易理财系统https://edu.csdn.net/course/detail/35475前言今年开始逐步切入产品与数据工作,完全脱离了原来的舒适区,确实有一些挑战。开始以为只做数仓建设的事情,就仓促的学习了一些数仓相关的知识,但没搞多久,还要负责公司BI的工作,又开始补习数分相关的知识。BI其实比数仓建设更有挑战性,数仓建设已经有非常成熟的体系了,市面上有很多

    2022年4月29日
    229
  • Thinkphp集成抖音SDK的实现方法[通俗易懂]

    Thinkphp集成抖音SDK的实现方法

    2022年2月15日
    54
  • NumPy-读写文件「建议收藏」

    NumPy-读写文件「建议收藏」读写文件NumPy文件读写主要有二进制的文件读写和文件列表形式的数据读写两种形式(1)save函数是以二进制的格式保存数据。  格式:  np.save("./save_arr",arr1)(2)load函数是从二进制的文件中读取数据。  格式:  np.load("./save_arr.npy")(3)savez函数可以将多个数组保存到一个文件中。  …

    2022年5月8日
    48

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号