在 Cacheable 注释中的条件下,如何使用密钥
How do I use the key, in a condition in Cacheable annotation
我正在使用@cacheable 注释缓存函数的结果。
我有 3 个不同的缓存,每个缓存的键是当前登录用户的用户 ID 与方法中的参数连接。
在某个事件中,我想驱逐所有具有以该特定用户 ID 开头的键的缓存条目。
例如:
@Cacheable(value = "testCache1", key = "'abcdef'")
我希望缓存逐出注释类似于:
@CacheEvict(value = "getSimilarVendors", condition = "key.startsWith('abc')")
但是当我尝试实现它时它给了我一个错误:
Property or field 'key' cannot be found on object of type'org.springframework.cache.interceptor.CacheExpressionRootObject' - maybe not public?
实现这个的正确方法是什么?
所有 Spring 缓存注解(即 @Cacheable
、@CacheEvict
等)在每个操作中处理 1 个缓存条目。 @CacheEvict
确实支持清除整个缓存(使用 allEntries
属性,但是在这种情况下忽略了键),但它在清除基于键模式的部分条目集时不是选择性的(能够)正如您所描述的,只需一次操作。
这背后的主要原因是 Spring Cache interface abstraction itself, where the evict(key:Object) method takes a single key argument. But technically, it actually depends on the underlying Cache implementation (e.g. GemfireCache), which would need to support eviction on all entries who's keys match a particular pattern, which is typically, not the case for most caches (e.g. certainly not for GemFire, and not for Google Guava Cache either; see here and here.)
这并不是说你绝对不能实现你的目标。它只是不支持开箱即用的东西。
有趣的是,除去您的方法的一些技术问题,您的条件达到了您想要的某种程度……仅当密钥满足条件时才会发生缓存逐出。但是,您 @CacheEvict 注释的方法只是缺少 "key",因此出现错误。所以,像下面这样的东西会满足你条件下的 SpEL...
@CacheEvict(condition = "#key.startsWith('abc')")
public void someMethod(String key) {
...
}
但是,在这种情况下,您必须将密钥指定为参数。但是,您不需要特定的键,您想要一个匹配多个键的模式。所以,放弃条件,只使用...
@CacheEvict
public void someMethod(String keyPattern) {
...
}
例如,使用 Guava 作为缓存提供程序,您现在需要提供 "custom" 扩展 GuavaCache.
的实现
public class CustomGuavaCache extends org.springframework.cache.guava.GuavaCache {
protected boolean isMatch(String key, String pattern) {
...
}
protected boolean isPattern(String key) {
...
}
@Override
public void evict(Object key) {
if (key instanceof String && isPattern(key.toString()))) {
Map<String, Object> entries = this.cache.asMap();
Set<String> matchingKeys = new HashSet<>(entries.size());
for (String actualKey : entries.keySet()) {
if (isMatch(actualKey, key.toString()) {
matchingKeys.add(actualKey);
}
}
this.cache.invalidateAll(matchingKeys);
}
else {
this.cache.invalidate(key);
}
}
}
现在只需扩展 GuavaCacheManager 即可插入您的 "custom" GuavaCache
(CustomGuavaCache
)...
public class CustomGuavaCacheManager extends org.springframework.cache.guava.GuavaCacheManager {
@Override
protected Cache createGuavaCache(String name) {
return new CustomGuavaCache(name, createNativeGuavaCache(name), isAllowNullValues());
}
}
这种方法利用了 Guava 的 Cache's invalidateAll(keys:Iterable) 方法。而且,当然,您可以使用 Java 的正则表达式支持在 isMatch(key, pattern)
方法中对要驱逐的所需键执行 "matching"。
所以,我没有测试过这个,但是这个(或类似的东西)应该(几乎)达到你想要的(祈祷 ;-)
希望对您有所帮助!
干杯,
约翰
我正在使用@cacheable 注释缓存函数的结果。 我有 3 个不同的缓存,每个缓存的键是当前登录用户的用户 ID 与方法中的参数连接。 在某个事件中,我想驱逐所有具有以该特定用户 ID 开头的键的缓存条目。 例如:
@Cacheable(value = "testCache1", key = "'abcdef'")
我希望缓存逐出注释类似于:
@CacheEvict(value = "getSimilarVendors", condition = "key.startsWith('abc')")
但是当我尝试实现它时它给了我一个错误:
Property or field 'key' cannot be found on object of type'org.springframework.cache.interceptor.CacheExpressionRootObject' - maybe not public?
实现这个的正确方法是什么?
所有 Spring 缓存注解(即 @Cacheable
、@CacheEvict
等)在每个操作中处理 1 个缓存条目。 @CacheEvict
确实支持清除整个缓存(使用 allEntries
属性,但是在这种情况下忽略了键),但它在清除基于键模式的部分条目集时不是选择性的(能够)正如您所描述的,只需一次操作。
这背后的主要原因是 Spring Cache interface abstraction itself, where the evict(key:Object) method takes a single key argument. But technically, it actually depends on the underlying Cache implementation (e.g. GemfireCache), which would need to support eviction on all entries who's keys match a particular pattern, which is typically, not the case for most caches (e.g. certainly not for GemFire, and not for Google Guava Cache either; see here and here.)
这并不是说你绝对不能实现你的目标。它只是不支持开箱即用的东西。
有趣的是,除去您的方法的一些技术问题,您的条件达到了您想要的某种程度……仅当密钥满足条件时才会发生缓存逐出。但是,您 @CacheEvict 注释的方法只是缺少 "key",因此出现错误。所以,像下面这样的东西会满足你条件下的 SpEL...
@CacheEvict(condition = "#key.startsWith('abc')")
public void someMethod(String key) {
...
}
但是,在这种情况下,您必须将密钥指定为参数。但是,您不需要特定的键,您想要一个匹配多个键的模式。所以,放弃条件,只使用...
@CacheEvict
public void someMethod(String keyPattern) {
...
}
例如,使用 Guava 作为缓存提供程序,您现在需要提供 "custom" 扩展 GuavaCache.
的实现public class CustomGuavaCache extends org.springframework.cache.guava.GuavaCache {
protected boolean isMatch(String key, String pattern) {
...
}
protected boolean isPattern(String key) {
...
}
@Override
public void evict(Object key) {
if (key instanceof String && isPattern(key.toString()))) {
Map<String, Object> entries = this.cache.asMap();
Set<String> matchingKeys = new HashSet<>(entries.size());
for (String actualKey : entries.keySet()) {
if (isMatch(actualKey, key.toString()) {
matchingKeys.add(actualKey);
}
}
this.cache.invalidateAll(matchingKeys);
}
else {
this.cache.invalidate(key);
}
}
}
现在只需扩展 GuavaCacheManager 即可插入您的 "custom" GuavaCache
(CustomGuavaCache
)...
public class CustomGuavaCacheManager extends org.springframework.cache.guava.GuavaCacheManager {
@Override
protected Cache createGuavaCache(String name) {
return new CustomGuavaCache(name, createNativeGuavaCache(name), isAllowNullValues());
}
}
这种方法利用了 Guava 的 Cache's invalidateAll(keys:Iterable) 方法。而且,当然,您可以使用 Java 的正则表达式支持在 isMatch(key, pattern)
方法中对要驱逐的所需键执行 "matching"。
所以,我没有测试过这个,但是这个(或类似的东西)应该(几乎)达到你想要的(祈祷 ;-)
希望对您有所帮助!
干杯, 约翰