缓存条目Listener
事件与Listener
JCache中定义了缓存事件的抽象类CacheEntryEvent<K,V>,以及事件类型EventType枚举类,包括了四种事件类型,定义见下:
public enum EventType {
CREATED, //创建
UPDATED, //更新
REMOVED, //删除
EXPIRED //过期
}
这些事件将被传播到注册在Cache中的CacheEntryListener,这个接口是一个标记接口,与四种事件类型对应的Listener接口都继承了这个接口,分别是CacheEntryCreatedListener、CacheEntryUpdatedListener、CacheEntryRemovedListener和CacheEntryExpiredListener。
Listener的注册
Listener并不一定是在Cache的进程内的,为了避免可能不支持序列化实例的注册,需要使用CacheEntryListenerConfigurations(具体类MutableCacheEntryListenerConfiguration)。
开发可在配置时通过MutableConfiguation.addCacheEntryListenerConfiguration方法增加缓存的Listener配置,或在运行期通过Cache.registerCacheEntryListener方法来注册Listener。Listener可以通过Cache.deregisterCacheEntryListener方法来注销注册。
多个CacheEntryListenerConfiguration可以增加到Configuration中,当缓存初始化时,它使用注册的Factory来创建CacheEntryListener。针对一个缓存可以存在多个Listener对应相同或者不同的EventType。在Listener创建或在其间派发事件,框架并不能保证其先后顺序。
Listener的调用
缓存Listener
●在缓存条目变化后被触发。
●在同步方式,对于一个给定的键,以该事件发生的顺序触发Listener,并阻塞调用线程,直到Listener返回。
●在异步方式,以未定义的顺序遍历多个事件,但针对相同的Key,事件必须按照发生的顺序进行处理。
Listener是观察者模式,同时Listener抛出一个异常并不会导致缓存操作失败。
在一个Listener中修改缓存的值可能会导致死锁,检测和响应死锁是特定于实现的。
缓存实现中已注册的Listener针对每个事件将最多被调用一次。
一个Listener并不一定在事件发生的进程内执行,在分布式环境中Listener可以在任何地方实现。
Listener可以有一个CacheEntryEventFilter,通过CacheEntryListenerConfiguration来配置。
Filter和同样Listener,它并不一定在事件发生的进程内执行。在分布式环境中,Filter可能实现在任何能提供最好性能的节点处。
下表总结了由每个缓存操作调用的Listener操作。条件是在操作前的条目的状态,过期始终是“否”,过期的确切时间基于缓存的特定实现。
方法 | 创建 | 过期 | 删除 | 更新 |
boolean containsKey(K key) | 否 | 否 | 否 | 否 |
V get(K key) | 是, 如果是通过read-through创建 | 否 | 否 | 否 |
Map<K,V> getAll(Collection<? extends K> keys) | 是, 如果是通过read-through创建 | 否 | 否 | 否 |
V getAndPut(K key, V value) | 是,如果不存在 | 否 | 否 | 是,如果存在 |
V getAndRemove(K key) | 否 | 否 | 是,如果存在 | 否 |
V getAndReplace(K key, V value) | 否 | 否 | 否 | 是,如果存在 |
<T> T invoke(K key, EntryProcessor<K, V> entryProcessor); | 是, 如果调用setValue()创建或者 调用getValue()通过read-through创建 | 否 | 是, 如果调用remove() | 是, 如果调用 setValue() 更新 |
<T> Map<K, T> invokeAll(Set<? extends K> keys, EntryProcessor<K, V, T> entryProcessor, Object... arguments); | 是, 如果调用setValue()创建或者 调用getValue()通过read-through创建 | 否 | 是, 如果调用remove() | 是, 如果调用 setValue() 更新 |
Iterator<Cache.Entry<K, V>> iterator() | 否 | 否 | 是, 如果调用remove() | 否 |
void loadAll(Set<? extends K> keys,boolean replaceExistingValues, CompletionListener completionListener);
| 是,如果不存在 | 否 | 否 | 是,如果存在 |
void put(K key, V value) | 是,如果不存在 | 否 | 否 | 是,如果存在 |
void putAll(Map<? extends K,? extends V> map) | 是,如果不存在 | 否 | 否 | 是,如果存在 |
boolean putIfAbsent(K key, V value) | 是,如果不存在 | 否 | 否 | 否 |
boolean remove(K key) | 否 | 否 | 是,如果存在 | 否 |
boolean remove(K key, V oldValue) | 否 | 否 | 是,如果存在并且相等 | 否 |
void removeAll() | 否 | 否 | 是,如果存在 | 否 |
void removeAll(Set<? extends K> keys) | 否 | 否 | 是,如果存在 | 否 |
boolean replace(K key, V value) | 否 | 否 | 否 | 是,如果存在 |
boolean replace(K key, V oldValue, V newValue) | 否 | 否 | 否 | 是,如果存在并且相等 |
缓存条目执行器
一个javax.cache.Cache.EntryProcessor是一个可调用的功能,就像一个java.util.concurrent.Callable,应用程序可以用它来有效地执行组合的原子性的缓存操作,包括访问,更新和缓存条目,而不需要显式的锁或事务。
例如,可能希望检查一个缓存条目的值,计算出新的值,更新条目,并返回一些其他的值的原子操作,一个应用程序可以使用自定义的EntryProcessor实现来完成此功能。
javax.cache.Cache.EntryProcessor定义见下:
public interface EntryProcessor<K, V,T> {
/**
* 处理一个条目。
* @param entry 缓存条目
* @param arguments 处理的参数集合
* @return 处理的结果
*/
T process(Cache.MutableEntry<K, V> entry, Object... arguments);
}
为了在缓存条目上调用EntryProcessor,应用必须使用Cache.invoke方法调用单个key的处理器及使用Cache.invokeAll调用一个key集合的处理器。
下面的例子演示了使用EntryProcessor自动增加缓存条目的值。
CachingProvider provider = Caching.getCachingProvider();
CacheManager manager = provider.getCacheManager();
MutableConfiguration<String, Integer> configuration =
new MutableConfiguration<String, Integer>()
.setTypes(String.class, Integer.class);
manager.createCache("example", configuration);
Cache<String, Integer> cache = manager.getCache(
"example", configuration);
String key = "counter";
cache.put(key, 1);
int previous = cache.invoke(key,
newIncrementProcessor<String>());
assert previous == 1;
assert cache.get(key) == 2;
IncrementProcessor定义如下。
public static class IncrementProcessor<K>
implements Cache.EntryProcessor<K, Integer, Integer> {
@Override
public Integer process(Cache.MutableEntry<K, Integer> entry,
Object... arguments){
if (entry.exists()) {
Integer current = entry.getValue();
entry.setValue(current + 1);
return current;
} else {
entry.setValue(0);
return -1;
}
}
}
支持远程或分布式缓存拓扑的实现可以选择在远程进程中执行条目处理器。在这种情况下实现可能需要获取EntryProcessors,调用的参数和返回类型实现java.lang.Serializable或以某种方式序列化。另外一个可选方法是实现可以选择简单序列化EntryProcessor类名,连同指定的调用参数,通过在远程实例化EntryProcessor类和反序列化调用的参数来执行远程调用请求。
当一个EntryProcessor被调用时,一个应用程序将永远不会看到其调用MutableEntry的getValue,etValue, remove方法产生的中间事件或副作用,而只能观察一个EntryProcessor上对缓存条目进行操作的最终结果。