JCache简介
JCache是JSR-107规范中定义了Java对象临时缓存在内存中的API和语义,包括对象的创建(object creation)、共享访问(shared access)、失效(invalidation)和跨JVM的一致性(consitency across jvm's)。
简单说就是:JCache是Java提供的标准缓存API。
JSR(Java Sepecification Requests),即Java规范提案。
下面是JSR-107规范中定义对于Java cache的定义。
JSR-107
核心概念
在JCache中定义了五个核心接口:CachingProvider、CacheManager、Cache、Entry和ExpiryPolicy。
- CachingProvider用于创建、配置、获取、管理和控制零个或多个CacheManager。应用程序在运行时可以访问或使用零个或多个CachingProvider。
- CacheManager用于创建、配置、获取、管理和控制零个或多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager只能归属一个CachingProvider。
- Cache是类似Map的数据结构,用于临时存储基于key的value。一个Cache只能归属一个CacheManager。
- Entry是存储在Cache中的键值对。
- ExpiryPolicy定义了每个Entry在Cache中有效时间(TTL),有效期内Entry能够被访问、修改和删除,有效期后该Entry不能在被访问、修改和删除。
对应关系如下:
值存储和引用存储
JSR-107中定义了两种Cache存储Entry的方法,即按值存储(Store-By-Value)和按引用存储(Store-By-Reference)。
- 按值存储是默认机制,也就是在存储kv对时,先对key和value进行拷贝,然后将拷贝的副本存储到Cache中。当访问Cache时,返回的是数据的副本。
- 按引用存储是另外一种可选的机制,存储kv对时,Cache中存储的是Key和Value的引用。当应用程序修改kv对时,应用程序无需再次修改Cache中的数据。
一致性
一致性是指当并发访问缓存时,需要保证修改的可见性在并发线程/进程间是一致的。为了保证一致性,所有的实现框架都应该支持以下默认一致性模型。
默认一致性模型
在执行大部分Cache操作时,就好像为Cache中的每个Key加了锁,当某个操作获取该key的排它性读写锁时,后面对该key的所有操作都会被阻塞,直到这个锁释放。注意,这里的操作可以是单个JVM进程内的,也可以是跨JVM的操作。
对于某些具有返回值的Cache操作,返回的缓存值需要是最新值。但是这个最新值,可以根据缓存的具体实现定义,比如当并发修改时,这个返回值可以是修改前的值,也可以是修改后的值。
Cache和Map的差异
Cache和Map两者之间有一些共性点,但是二者不是同一个东西。下面是二者之间的相同点与不同点:
相同点:
- 都是通过Key进行存储和访问。
- 每个Key都与一个Value对应。
- 当使用可变对象作为Key时,修改Key值可能引发查询对比的未知。
- 使用自定义类作为Key时,都需要显示实现Object.hashCode()方法。
不同点:
- Cache的Key和value不许为null,如果设置了null,则会抛出NullPointerException。
- Cache中的Entry可能会过期(Expire)。
- Cache中的Entry可能被驱逐(Evicted)。
- 为了支持原子比较和交换(compare-and-swap, CAP),Cache中的自定义应提供Object.equals()方法的实现。
- Cache中的Key和Value是需要可被序列化的。
- Cache可以设置使用值存储或引用存储来存储Entry实例。
- Cache是可以选择强制的安全性限制,如果违规操作,可以抛出SecurityException异常。
JCache demo
引入依赖:
<dependency> <groupId>javax.cache</groupId> <artifactId>cache-api</artifactId> <version>1.1.1</version> </dependency>
下面是JCache的使用demo。
CachingProvider provider = Caching.getCachingProvider(); CacheManager cacheManager = provider.getCacheManager(); MutableConfiguration<String, Integer> config = new MutableConfiguration<>(); config.setTypes(String.class, Integer.class); config.setExpiryPolicyFactory(AccessedExpiryPolicy.factoryOf(Duration.ONE_HOUR)); Cache<String, Integer> cache = cacheManager.createCache("testCache", config); cache.put("test1", 1); cache.put("test2", 2); System.out.println(cache.get("test1")); System.out.println(cache.get("test2")); cache.remove("test1"); System.out.println(cache.get("test2"));
需要注意的是我们没有提供实现框架,所以运行过程中会抛出以下异常,原因是JVM找不到getCacheManager()方法的任何实现。
Exception in thread "main" javax.cache.CacheException: No CachingProviders have been configured
源码分析
下面是JCache的源码,比较精简。
CachingProvider
CachingProvider是一个接口,我们可以用于创建和管理CacheManager的生命周期。
public interface CachingProvider extends Closeable { CacheManager getCacheManager(URI uri, ClassLoader classLoader, Properties properties); ClassLoader getDefaultClassLoader(); URI getDefaultURI(); Properties getDefaultProperties(); CacheManager getCacheManager(URI uri, ClassLoader classLoader); CacheManager getCacheManager(); void close(); void close(ClassLoader classLoader); void close(URI uri, ClassLoader classLoader); boolean isSupported(OptionalFeature optionalFeature); }
CacheManager
CacheManager是JCache API最重要的接口之一,他提供了创建、配置和销毁Cache的方法。
public interface CacheManager extends Closeable { CachingProvider getCachingProvider(); URI getURI(); ClassLoader getClassLoader(); Properties getProperties(); <K, V, C extends Configuration<K, V>> Cache<K, V> createCache(String cacheName, C configuration) throws IllegalArgumentException; <K, V> Cache<K, V> getCache(String cacheName, Class<K> keyType, Class<V> valueType); <K, V> Cache<K, V> getCache(String cacheName); Iterable<String> getCacheNames(); void destroyCache(String cacheName); void enableManagement(String cacheName, boolean enabled); void enableStatistics(String cacheName, boolean enabled); void close(); boolean isClosed(); <T> T unwrap(java.lang.Class<T> clazz); }
Cache & Entry
Entry是Cache的内部接口:
public interface Cache<K, V> extends Iterable<Cache.Entry<K, V>>, Closeable { V get(K var1); Map<K, V> getAll(Set<? extends K> var1); boolean containsKey(K var1); void loadAll(Set<? extends K> var1, boolean var2, CompletionListener var3); void put(K var1, V var2); V getAndPut(K var1, V var2); void putAll(Map<? extends K, ? extends V> var1); boolean putIfAbsent(K var1, V var2); boolean remove(K var1); boolean remove(K var1, V var2); V getAndRemove(K var1); boolean replace(K var1, V var2, V var3); boolean replace(K var1, V var2); V getAndReplace(K var1, V var2); void removeAll(Set<? extends K> var1); void removeAll(); void clear(); <C extends Configuration<K, V>> C getConfiguration(Class<C> var1); <T> T invoke(K var1, EntryProcessor<K, V, T> var2, Object... var3) throws EntryProcessorException; <T> Map<K, EntryProcessorResult<T>> invokeAll(Set<? extends K> var1, EntryProcessor<K, V, T> var2, Object... var3); String getName(); CacheManager getCacheManager(); void close(); boolean isClosed(); <T> T unwrap(Class<T> var1); void registerCacheEntryListener(CacheEntryListenerConfiguration<K, V> var1); void deregisterCacheEntryListener(CacheEntryListenerConfiguration<K, V> var1); Iterator<Cache.Entry<K, V>> iterator(); public interface Entry<K, V> { K getKey(); V getValue(); <T> T unwrap(Class<T> var1); } }
Configuration
配置类:
ExpiryPolicy
KV过期策略:
参考
https://www.jcp.org/en/jsr/detail?id=107
https://www.javadoc.io/doc/javax.cache/cache-api/1.0.0/javax/cache/package-summary.html
https://docs.oracle.com/middleware/1213/coherence/develop-applications/jcache_intro.htm#COHDG5778
https://docs.google.com/document/d/1ijduF_tmHvBaUS7VBBU2ZN8_eEBiFaXXg9OI0_ZxCrA/edit