系统中集成了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
