Mejore Spring @CacheEvict para lograr la eliminación de coincidencias difusas clave

          Spring cache está integrado en el sistema. @CacheEvict se usa para borrar el caché. @CacheEvict puede borrar la clave especificada y, al mismo tiempo, puede especificar allEntries = true para borrar todos los elementos bajo el espacio de nombres. Ahora se encuentra con un problema . Use allEntries = true para borrar el espacio de nombres. El valor solo puede ser Constante, pero ahora necesito separar el caché según el TelnetID único del inquilino, lo que hace que allEntries = true no esté disponible. De lo contrario, una vez que se borre el caché activada, todas las cachés se borrarán, y solo quiero borrar la caché del inquilino actual. Aquellos que están familiarizados con los comandos de redis saben que tanto la consulta como la eliminación pueden realizar coincidencias difusas, por lo que quiero que @CacheEvict de SpringCache también admita difusa eliminación coincidente.

         Primero, descubra cómo @CacheEvict implementa la limpieza de caché, porque he visto el código fuente de redis antes, y sé que @CacheEvict se implementa a través de AOP, y la clase principal es CacheAspectSupport. Para los detalles específicos del análisis del código fuente, puede Búscalo tú mismo en Google. Simplemente analizo los métodos clave en la clase 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;
	}

Este método es el método principal del proceso de entrada de control de cachéCacheEvicts, que limpiará la caché de acuerdo con CacheOperationContext. Podemos ver un método performCacheEvict llamado

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);
			}
		}
	}

Al ver esto, sabemos lo que está sucediendo, continuamos depurando y realizando un seguimiento, y vemos que doClear y doEvict eventualmente llamarán a los métodos evict y clear en RedisCache respectivamente.

@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);
	}

Vemos que si allEntries es ture, el método clear () finalmente se ejecuta. De hecho, también se borra vagamente, pero su regla clave es espacio de nombres :: *. Viendo aquí, podemos ver esperanza. Solo tenemos que pensar en Insertar nuestro telnetID en el espacio de nombres * puede convertirse en espacio de nombres :: telnetID: * este formato, que ha logrado nuestro propósito.

   El punto clave es reescribir el método de desalojo de RedisCache, crear un nuevo RedisCacheResolver para integrar RedisCache y reescribir el método de desalojo.

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);
    }
}

     Ahora tenemos que hacer que nuestro RedisCacheResolver surta efecto, por lo que necesitamos inyectar nuestro RedisCacheResolver en RedisCacheManager, por lo que debemos definir nuestro propio RedisCacheManagerResolver para integrar 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);
    }
}

        Hasta ahora hemos completado los pasos clave y, finalmente, solo necesitamos administrar nuestro propio RedisCacheManagerResolver en RedisConfig.

	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;
	}

      Después de los pasos anteriores, hemos implementado el método de desalojo de reescritura para eliminar la caché borrosa

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

Simplemente use la anotación anterior, podemos eliminar todas las cachés en telnetID

Supongo que te gusta

Origin blog.csdn.net/Crystalqy/article/details/110681684
Recomendado
Clasificación