原文网址:http://rmn190.iteye.com/blog/792734
早就想系统一点研究研究Ehcache了, 现在时机成熟了些, 于是着手这方面的研究.
Ehcache是干啥的? 它跟别的同类产品相比有什么优势? 这些问题相信大家已有所了解,本篇先不列举,这里先搭建一个很简单地例子,以便能有一个debug方式研究ehcache的活场景.
这个例子很简单,以AOP的方式配置了Spring+Ehcache, 并没有直接用相对新特性(ehcache:config ). 一是,新特性是基于传统的AOP发展来的,用传统的方式更能从根上来理解问题;二是,基于这个传统方式,随着对其不足的认识,一步步地去理解新特性,这样不仅仅对Ehcache有更深的认识,相信对自己的编码/设计也会有不小的提高.
具体配置不再赘述,详见附件.
这里先大致总结下实例中的配置思路. 总体来说, 实例是基于AOP的,即利用拦截器的特性来处理缓存. 实例中写了一个模拟从数据库里取Person的方法(PersonManagerImpl类中的getList), Spring中以ProxyFactoryBean方式给它配置了一个代理,这样当getList方法调用时, 拦截器先从cache里看看有没有想要的数据,如果有直接从cache里取,如果没有真正调用getList方法并将结果new一个Element从而缓存起来.
这个实例中只是用到了取数据时的缓存,但若数据库里真正数据有更新时并没有考虑, 这些特性以后会逐步以实验的方式加上. 敬请关注!
注: 跟笔者其它研究源码的博客一样, Ehcache系列也是基于一个例子来debug地跟踪, 例子详见Ehcache(一): Spring + Ehcache开场白中的附件.如果没有例子作参照,阅读过程中可能有些摸不着头绪.
--------------------
先看实例中的applicationContext配置文件说起. 配置文件中第一个bean是EhCacheManagerFactoryBean, 那么它是干啥的? 看源码,我们得知它封装了三个属性(CacheManager类型的cacheManager,boolean类型的shared和表示ehcache配置信息的configLocation). 这个类很简单,从类名和封装了的属性上也不难看出Spring用些类(afterPropertiesSet方法)来new出一个 CacheManager实例. CacheManager是Ehcache赖以运行的后防基地,这个不必多说.
不过看afterPropertiesSet方法,它有对shared的判断. 这是干啥的? 看源码注释发现了这样的描述: whether the EHCache CacheManager should be shared (as a singleton at the VM level) or independent (typically local within the application). 也说是说通过这个来设置cache的基地是这里的Spring独用,还是跟别的(如Hibernate的Ehcache共享). 这样了就回答了我心中一个问题: 如何让Hibernate也用到Spring中启动的Ehcache?
接下来据shared与否的设置,Spring分别通过CacheManager.create()或new CacheManager()方式来创建一个ehcache基地.
这样一个EhCacheManagerFactoryBean创建完成, 也就代表着一个CacheManager的启用.
下一篇中再看这个CacheManager怎么来使用?
Ehcache(二): 从EhCacheManagerFactoryBean说起 中, 我们看到一个EhCacheManagerFactoryBean的创建并由此启用一个CacheManager实例. 结合ehcache的配置文件和CacheManager的名字,不难猜出这个实例是管理Cache的. 那么这个CacheManager实例用在了哪? 配置文件中的org.springframework.cache.ehcache.EhCacheFactoryBean实例的创建中. 那么又用这个CacheManager实例做了些什么呢? 这得看EhCacheFactoryBean类的afterPropertiesSet方法.
方法afterPropertiesSet中有这么一段代码:
if (this.cacheManager.cacheExists(this.cacheName)) {
if (logger.isDebugEnabled()) {
logger.debug("Using existing EHCache cache region '" + this.cacheName + "'");
}
this.cache = this.cacheManager.getEhcache(this.cacheName);
}
也就是说, cacheManager会检查下配置的cacheName(即,ehcache.xml文件中名为com.rmn190.MethodCache的cache)对应的cache是否已经存在. 如果存在,就直接get出来.
这只是get了下cacheManager中已有的cache,那么那个已有的cache是怎么创建出来的? 也就是本例中的名为com.rmn190.MethodCache的cache的cache是何时/如何创建的?
一番顺藤模瓜后, 找到了ConfigurationHelper类中的createCache(CacheConfiguration cacheConfiguration)方法. 这里真真切切地看到了"new Cache"的调用.
上面我们深层次地体会到Cache实例的创建并通过cacheManager给get了出来, get出来后,在Spring中就set给了例子中MethodCacheInterceptor类属性cache. 不过这里又有问题了: MethodCacheInterceptor类属性cache是一net.sf.ehcache.Cache类型的, 但Spring配置文件中set来的实例是一个 org.springframework.cache.ehcache.EhCacheFactoryBean,类型不匹配的,EhCacheFactoryBean与Cache有继承或实现关系? 看源码,没有发现. 那Spring又是怎么解决这个类型不匹配问题的?
我们在EhCacheFactoryBean类实现的接口FactoryBean上找到了答案: getObject和getObjectType. 通过FactoryBean接口定义两个方法给出的信息,Spring就很自然而流畅地解决了类型匹配问题.
Ehcache(三): Cache实例的get与set 中, 我们看到一个Cache实例从CacheManager中get了出来,并又set给了MethodCacheInterceptor. 那么不禁要问: 这个get/set的Cache实例代表什么信息?
家谱
本着尽可能多地吸收开源项目精华的原则, 我们还是来看Cache类的家谱. 在Eclipse中F4后,我们得到了如下所示的继承/实现关系.
如图所示, Cache类现了Ehcache接口. 再看Ehcache接口又有什么特性呢? Ehcache接口里有定义了很多方法,不过我们这里只关注现在要用到的: get,put,remove.这几个方法也折射出Cache的实质: 把数据放到缓存中, 从缓存中取出数据和从缓存中删除掉不再有意义的数据.
上面我们从最根上看出Cache是Ehcache中存放数据的Cell,那么为了实现Ehcache接口中定义的方法(也即缓存的基本功能),这个Cache类又依赖什么或又有什么辅助属性呢? 我们看到有这样的属性:
1, 真正存放数据的地方: diskStore(net.sf.ehcache.store.Store类型) , memoryStore(net.sf.ehcache.store.MemoryStore类型).
2, 与命中与否相关的统计信息: hitCount,memoryStoreHitCount,diskStoreHitCount,missCountNotFound,missCountExpired
3, 与事件相关的监听: registeredEventListeners.
何时创建/创建时用到什么信息?
Ehcache(三): Cache实例的get与set 中我们顺藤摸瓜地看到一个Cache实例是在(或者说可以在)ConfigurationHelper类的createCache(CacheConfiguration cacheConfiguration)方法创建出来. 方法中我们看到了如下代码:
Cache cache = new Cache(cacheConfiguration.name,
cacheConfiguration.maxElementsInMemory,
cacheConfiguration.memoryStoreEvictionPolicy,
cacheConfiguration.overflowToDisk,
getDiskStorePath(),
cacheConfiguration.eternal,
cacheConfiguration.timeToLiveSeconds,
cacheConfiguration.timeToIdleSeconds,
cacheConfiguration.diskPersistent,
cacheConfiguration.diskExpiryThreadIntervalSeconds,
null,
null,
cacheConfiguration.maxElementsOnDisk,
cacheConfiguration.diskSpoolBufferSizeMB);
构造方法中绝大多数地用到了cacheConfiguration实例, 写到这里相信大家也都能看出来了: 一个Cache对象对应着ehcache.xml配置文件中一个<cache>标签. 也即如下的配置信息:
<cache name="com.rmn190.MethodCache"
maxElementsInMemory="10"
eternal="false"
timeToIdleSeconds="200"
timeToLiveSeconds="300"
overflowToDisk="true"
/>
也就是说,一个Cache对象对应着Ehcache项目在内存或磁盘里根据<cache>标签里信息划分出来的一个存储空间,为了使用上的方便,给这存储空间起了个名字,即<cache>标签中的name信息.
1, checkStatus(): if (!status.equals(Status.STATUS_ALIVE))
2, element.resetAccessStatistics():
lastAccessTime = 0;
nextToLastAccessTime = 0;
hitCount = 0;
怎么把一个element里的这些信息都给置成了0?
3, elementExists = isElementInMemory(key) || isElementOnDisk(key);
判断要put进来的element是否已存在. 为什么不分是Memory和Disk呢?
Memory中用了Map来模拟内存,
disk判断时: diskElements.containsKey(key) || spool.containsKey(key).
private Map diskElements = Collections.synchronizedMap(new HashMap());
private Map spool = new HashMap();
怎么一点也看不出与disk相关的信息: 如,IO,stream等.
4, backOffIfDiskSpoolFull
看来这个方法里并不真正做back? 那么真正的backOff是在什么方法里做的?
5, 真正的put
怎么类MemoryStore里的doPut方法是空的?
7, registeredEventListeners.notifyElementXXX方法:
这些监听是怎么设置的?
跟实例对应的文章:Speed Up Your Hibernate Applications with Second-Level Caching. 这里为了研究这篇文章和Ehcache, 在Eclipse中重新配置了下,又补全了必要jar包。