这篇文章探索Java缓存的新标准:javax.cache。
怎么融入到Java生态系统(Java Ecosystem)
这个标准由JSR107所提出,它的作者同样也是标准制定的领导者。JSR107由JSR342提出,已经被包含在Java EE 7中。Java EE7将会在2012年底定稿。但现在在Java SE6或更新的版本和Java EE 6环境中,甚至Spring和另外一些流行的开发环境中,javax.cache都可以正常使用。
JSR107当前正处于起草阶段。我们当前最新的API发行版是0.3,同样也有它的参照实现和TCK。文章中的代码正是用的这个版本。
使用
专家团队中比较活跃或者有兴趣实现规范的厂商有:
- Terracotta – Ehcache
- Oracle – Coherence
- JBoss – Infinispan
- IBM – ExtemeScale
- SpringSource – Gemfire
- GridGain
- TMax
- Google App Engine Java
Terracotta将会根据最终的草稿发布一个Ehcache组件,在需要的情况下会更新。
特性
从设计的角度来看,基本的概念是一个CacheManager
保存和控制一系列的缓存。缓存有很多条目(entries)。基本的API可以被当做是一个类似map并拥有下面一些特点的东西:
- 原子操作,跟
java.util.ConcurrentMap
类似 - 从缓存中读取
- 写入缓存
- 缓存事件监听器
- 数据统计
- 包含所有隔离(ioslation)级别的事务
- 缓存注解(annotations)
- 保存定义key和值类型的泛型缓存
- 引用保存(只适用于堆缓存)和值保存定义
可选特性
我们通过另外一种方法,而不是把规范拆分成针对如Java SE和Spring/EE不同用户群体的多个版本。
首先,对Java SE方式的缓存,它是没有任何依赖的。而对于Spring/EE你想要使用注解(annotations)和/或者事务,依赖将由这些框架提供。
其次,我们提供了一个ServiceProvider.isSupported(OpertionalFeature feature)
功能API,所以你可以在运行时决定实现的功能是什么。可选的特点如下:
- storeByReference – 默认storeByValue
- 事务性
- 注解
这使得各个API实现可以在不用实现所有特性的情况下支持规范,并且允许终端用户和框架去探索那些功能是什么,以便动态地配置合适的使用。
对单个和分布式的缓存的好处
虽然规范中没有要求特别的分布式缓存结构,但实际上已经是默认缓存是分布的。我们有一个API可以覆盖这两种使用,但对分布式比较敏感。例如CacheEntryListener
有一个它监听的事件的NotificationScope
,因此事件可以被限制在本地的传递。我们没有高网络消耗的类似map的如keySet()和values()的方法。并且,我们通常更倾向于不使用或使用低消耗的返回类型。所以Map有一个V put(K key, V value)
,javax.cache.Cache
有一个void put(K key, V value)
。
类加载
缓存包含了供多个线程使用的数据,而这些线程又可能是运行在同一个JVM下的不同的容器应用或者OSGI组件,并且可能在一个集群中的多个JVM中。这使得类加载比较困难。
我们设法解决了这个问题。在创建CacheManager
时可以指定一个classloader。如果没有指定,实现方式将会提供一个默认的。当对象被反序列化时,会使用CacheManager
的classloader。
这相比其他缓存如Ehcache等采用的后备方案是一个很大的进步。线程的上下文classloader首先被使用,如果它加载失败,则会尝试其他的classloader。这在大多数情况下都可以正常工作,但这还是有命中的问题,并且不同的实现方式有相当大的区别。
获取代码
标准的规范在Maven中文仓库中。Maven的代码段如下:
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<version>0.3</version>
</dependency>
API快速入门
创建一个CacheManager
我们也支持Java 6 java.util.ServiceLoader
创建方式。它会自动检测你的classpath中存在的缓存实现。接着,你可以创建通过下面的语句CacheManager
:
CacheManager cacheManager = Caching.getCacheManager();
它返回一个被称做“default”单例CacheManager
。在它之后的调用都会返回同一个CacheManager
。
CacheManagers can have names and classloaders configured in. e.g. CacheManager cacheManager = Caching.getCacheManager("app1", Thread.currentThread().getContextClassLoader()); Implementations may also support direct creation with new for maximum flexibility: CacheManager cacheManager = new RICacheManager("app1", Thread.currentThread().getContextClassLoader()); CacheManager可以通过下面的方式来配置名称和classloader: CacheManager cacheManager = new RICacheManager("app1", Thread.currentThread().getContextClassLoader());
各个实现考虑到最好的灵活性也支持通过new
进行直接的创建:
CacheManager cacheManager = new RICacheManager("app1", Thread.currentThread().getContextClassLoader());
或者可以在不添加任何具体实现到编译时依赖的情况下完成同样的事情:
String className = "javax.cache.implementation.RIServiceProvider"; Class<ServiceProvider> clazz = (Class<ServiceProvider>)Class.forName(className); ServiceProvider p