当前位置:网络安全 > 增强Spring - 改造@CacheEvict支持缓存批量模糊删除

增强Spring - 改造@CacheEvict支持缓存批量模糊删除

  • 发布:2023-09-29 21:51

系统集成spring缓存。使用 @CacheEvict 进行缓存清除。 @CacheEvict可以清除指定的key,可以指定allEntries = true清除命名空间下的所有元素。现在我有一个问题。使用 allEntries = true。清除命名空间的值只能是一个常量,但我现在需要根据租户唯一的TelnetID来分离缓存。这样就导致不能使用allEntries = true,否则一旦触发缓存清除,所有缓存都会被清除,而我只想清除当前租户的缓存。熟悉Redis命令的人都知道,查询和删除都可以进行模糊匹配,所以我想要SpringCache的@CacheEvict 至还支持模糊匹配清除。

LLET的第一个弄清楚@cacheevict如何实现缓存清洁,因为我以前已经看到了redis的源代码,并且知道@@cacheevict是通过AOP和核心类实现的就是CacheAspectSupport,具体源码分析细节大家可以自行Google,我只简单分析一下CacheAspectSupport这个类中的几个关键方法

private对象executefinalCacheOperationInvoker调用者,Method方法,CacheOperationContexts上下文) {
  // 特殊处理同步调用
  if (contexts.isSynchronized()) {
   CacheOperationContext context = contexts.get(CacheableOperation.).迭代器().下一个();
   if(isConditionPassing(上下文,CacheOperation) www.sychzs.cn_RESULT)) {
    对象键=generateKey(context, CacheOperationExpressionEvaluator. NO_RESULT);
    缓存缓存= context.getCaches().iterator().next();
    try {
    returnwrapCacheValue(方法,缓存。 get(key, () -> unwrapReturnValue(invokeOperation(invoker))));
    }
    catch(Cache.ValueRetrievalException ex){
     //调用者换行ThrowableWrapper 实例中的任何 Throwable,因此我们
     //确保其中一个在堆栈中冒泡。
      throw(CacheOperationInvoker.ThrowableWrapper)ex.getCause();
    }    }
   其他 {
    //无需缓存,只需调用底层方法
    returninvokeOperation(invoker);    }
  }
 
 
  // 处理任何早期驱逐
  processCacheEvicts(contexts.get(CacheEvictOperation.class),true ,
    CacheOperationExpressionEvaluator.NO_RESULT)
;
 
  //检查我们是否有与条件
  Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation) .class));
 
  //从任何 @Cacheable 未命中收集 put,如果未找到缓存项目
  列表cachePutRequests = new LinkedList<>();
  if (cacheHit == null ) {
   collectPutRequests(contexts.get(CacheableOperation.class),
     CacheOperationExpressionEvaluator. NO_RESULT
cachePutRequests

  }
 
  对象cacheValue;
  对象returnValue;

  if (cacheHit != null && !hasCachePut(上下文)) {
   //如果没有put请求,就使用缓存命中
   cacheValue = cacheHit.get();
   returnValue = wrapCacheValue(method,cacheValue);
  }
  其他 {
   //如果我们没有缓存命中,则调用该方法
   returnValue = invokeOperation(invoker);    cacheValue = unwrapReturnValue(returnValue);
  }
  //收集任何显式@CachePuts
  collectPutRequests(contexts.get(CachePutOperation.class), cacheValuecachePutRequests;
  //处理任何收集的 put 请求,无论是来自 @CachePut 还是 @Cacheable miss
  for (CachePutRequestcachePutRequest : cachePutRequests) {
cachePutRequest.apply(cacheValue);
}
// 处理任何后期驱逐 ProcessCacheEvicts(contexts.get) (CacheEvictOperation.), false, cacheValue);
return returnValue;
}

这个方法是缓存控制入口核心方法processCacheEvicts会根据CacheOperationContext进行缓存清理处理。我们可以看到一个名为 performCacheEvict Method

private void performCacheEvict
CacheOperationContext 上下文,CacheEvictOp操作,@Nullable 对象结果)
{

对象键 = null;
for(缓存缓存:context.getCaches()){
if (操作.isCacheWide()) { // 如果 allEntries 为 true,则执行此逻辑
logInvalidating(context, operation, null);
doClear(cache);
}
其他 { // 否则,删除指定键
if (key == null ) {
key =generateKey(上下文, 结果);
                                                                                                                                          。
}

看到这里,我们就知道是怎么回事了。我们继续调试和跟进。我们看到doClear和doEvict最终会调用RedisCache

中的evict和clear方法
@Override
 public void evict(对象键)  {
  cacheWriter.remove(name, createAndConvertCacheKey (关键));
 }
 
 /*
  * (非javadoc)
  * @see org.springframework.cache.Cache#清除()
  */

 @Override
 public void  清除() {
    //支持模糊删除
  byte[] pattern = conversionService.convert(createCacheKey("*" ),字节[] .);
  cacheWriter.clean(名称, 模式);
 }

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

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

public class RedisCacheResolver 扩展  RedisCache {
 
    私有 final 字符串名称;
    私有 final RedisCacheWriter cacheWriter;
    private finalConversionService conversionService;
 
    受保护 RedisCacheResolver(字符串名称,RedisCacheWritercacheWriter,RedisCacheConfiguration缓存配置) {
        超级(名称,cacheWriter,cacheConfig);
        这个.name =名称;
        这个 .cacheWriter = cacheWriter;
        这个.conversionService = cacheConfig.getConversionService();
    }
 
    /**
     * 
      * @Title:驱逐
      * @Description: 重写删除的方法
      * @param  @param key       * @throws 
      *
     */

    @Override
public void evict(对象键) {
//如果按键包含“ noCacheable :"关键字不会被缓存
    if
(key.toString().contains(www.sychzs.cn_CACHEABLE)) {
    返回
;
{
                                         keyString                                             key.toString ();               if (StringUtils.endsWith(keyString, "*")) {
           evictLikeSuffix(keyString);
                                                                         evictLikeSuffix(key)                               
                                                                                                                                                                                                                                                             我被驱逐(键);
}

/**
* * 后缀匹配匹配
* *
* 私人 evictLikeSuffix(字符串键) {
byte[] 模式 = 这个 .conversionService.convert(这个.createCacheKey(key), byte[].class);
               这个.cacheWriter。 clean(this.name,pattern);
}
}

现在我们需要制作这个Redis CacheResolver生效,所以我们需要改变我们的RedisCacheResolver被注入到RedisCacheManager,所以我们需要定义自己的RedisCacheManagerResolverz集成

public class RedisCacheManagerResolver 扩展  RedisCacheManager {
    私有 finalRedisCacheWritercacheWriter;
    私有 finalRe disCacheConfiguration defaultCacheConfig;
 
    public RedisCacheManagerResolver( RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
        super(cacheWriter, defaultCacheConfiguration);
        这个.cacheWriter = cacheWriter;
        这个.defaultCacheConfig = defaultCacheConfiguration;
    }
 
    public  RedisCacheManagerResolver(RedisCacheWritercacheWriter,RedisCacheConfigurationdefaultCacheConfiguration,String...initialCacheNames)
{
        super(cacheWriter,defaultCacheConfiguration,initialCacheNames);
        这个.cacheWriter=cacheWriter;
        这个.defaultCacheConfig = defaultCacheConfiguration;
    }
 
    public RedisCacheManagerResolver(RedisCacheWriter cacheWriter、 RedisCacheConfiguration defaultCacheConfiguration、布尔值allowInFlightCacheCreation,字符串...初始CacheNames){
        超级 (cacheWriter,defaultCacheConfiguration,allowInFlightCacheCreation,initialCacheNames);
        这个 .cacheWriter = cacheWriter;
        这个.defaultCacheConfig = defaultCacheConfiguration;
    }
      public RedisCacheManagerResolver(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration , Map 初始缓存配置) {
        super(cacheWriter, defaultCacheConfiguration , initialCacheConfigurations);
        这个.cacheWriter = cacheWriter;
这个.defailureCacheConfig = defaultCacheConfiguration;
    }
 
    public RedisCacheManagerResolver(RedisCacheWritercacheWriter、RedisCacheConfigurationdefaultCacheConfiguration、MapinitialCacheConfigurations、booleanallowInFlightCacheCreation)  {
        超级(cacheWriter、defaultCacheConfiguration、initialCacheConfigurations、allowInFlightCacheCreation);         这个.cacheWriter = cacheWriter;
        这个.defaultCacheConfig = defaultCacheConfiguration;
    }
 
    public RedisCacheManagerResolver(RedisConnectionFactory redisConnectionFactory, RedisCacheConfiguration cacheConfiguration) {
        这个(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),cacheConfiguration);
    }
 
    /**
     *覆盖父类创建RedisCache,采用自定义的RedisCacheResolver
@标题:createRedisCache 
      * @Description:TODO
      * @param  @param 姓名
      * @param  @paramcacheConfig
      * @param  @return        * @扔 
      *
     */

    @Override
    受保护 RedisCache createRedisCache(字符串名称,@Nullable RedisCacheConfig缓存配置) {
        返回 newRedisCacheResolver(名称,cacheWriter,cacheConfig!=null?cacheConfig:defaultCacheConfig);
    }
 
    @Override
     public 地图 getCacheConfigurations() {
        Map configurationMap = new HashMap<>(getCacheNames( ).size());
        getCacheNames().forEach(it -> {
            RedisCache缓存= RedisCacheResolver..演员) LookupCache(它? null);
            });
      返回 Collections.unmodifyingMap(configurationMap);
   }
}

此时我们关键步骤都完成了,最后我们只需要在RedisConfig RedisCacheManagerResolver中管理自己的

 public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { 
//设置全局过期时间,单位为秒
Duration timeToLive = www.sychzs.cn;
timeToLive = Duration.ofSeconds(timeOut);
// RedisCacheManager cacheManager = new RedisCacheManager(
// RedisCacheWriter.nonLockingRedisCacheWriter(re disConnectionFactory),
// this.getRedisCacheConfig urationWithTtl(生存时间), // 默认策略,未配置的值就使用这个
// this.getRedisCacheConfigurationMap() // 指定值策略
// );
RedisCacheManagerResolver cacheManager =
new RedisCacheManagerResolver(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
       这个.getRedisCacheConfigurationWithTtl(timeToLive), //默认策略,未配置的值就使用这个
.getRedisCacheConfigurationMap() // 指定取值策略
);

// RedisCacheManager cacheManager=RedisCacheManager.builder(factory).cacheDefaults(config).build();
returncacheManager;
}

通过在上面的步骤中,我们有实现重写 evict 方法,用于模糊删除缓存

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

只要使用上面的注释,我们就可以删除telnetID下的所有缓存

来源:www.sychzs.cn/Crystalqy/

文章/详情/110681684


后端专属技术组

打造高质量的技术交流社区。欢迎从事编程开发、技术招聘的HR人员加入。也欢迎大家分享自己公司的内部推荐信息,互相帮助,共同进步!

相关文章