Ehcache学习手册 |
学习收藏 |
|
whai |
2011/6/18 |
|
1. 文档介绍... 3
1.1. 文档目的... 3
1.2. 文档范围... 3
1.3. 读者对象... 3
1.4. 参考文献... 3
1.5. 术语及缩写解释... 3
2. 概述... 4
2.1. 背景... 4
2.2. 主要特性... 4
2.3. 环境... 4
2.4. 下载资源... 4
3. Ehcache缓存配置... 5
4. Ehcache页面缓存的配置... 9
4.1. Ehcache的类层次模型... 9
4.2. 环境搭建... 9
4.3. Ehcache配置文件中元素说明... 9
4.4. 在工程中单独使用... 12
5. 在 Spring中运用 EHCache. 13
6. 分布式缓存集群环境配置... 20
6.1. 集群配置方式... 20
为以后更加的了解和熟悉缓存技术所编写。
包含了ehcache缓存技术相关的内容,实现页面级的缓存以及完成集群设计的过程。
任何感兴趣的开发技术人员
大部分都是网络上查询的资料,很多,不列举了。
EHCache 简介:
http://apps.hi.baidu.com/share/detail/7491847
http://wangjicn.cn/data/read/9082403332378.html
http://blog.csdn.net/mgoann/archive/2009/04/16/4083179.aspx
http://yuanyong.javaeye.com/blog/691499
Spring 整合 EHCache:
http://wangjicn.cn/data/read/909291257438.html
http://www.yybean.com/ehcache-getting-started-series-5-a-distributed-cache-clus
ter-environment-configuration
http://zhyt710.javaeye.com/blog/333213
http://tech.ddvip.com/2010-04/1270187299149502.html
http://blog.csdn.net/goodboylllll/archive/2010/04/01/5442329.aspx
EHCache:EHCache 是一个快速的、轻量级的、易于使用的、进程内的缓存。它支持 read-only 和 read/write 缓存,内存和磁盘缓存。是一个非常轻量级的缓存实现,而且从 1.2 之后就支持了集群,即分布式。
系统缓存是位于应用程序与物理数据源之间,用于临时存放复制数据的内存区域,目的是为了减少应用程序对物理数据源访问的次数,从而提高应用程序的运行性能. 缓存设想内存是有限的,缓存的时效性也是有限的,所以可以设定内存数量的大小,可以执行失效算法,可以在内存满了的时候,按照最少访问等算法将缓存直接移除或切换到硬盘上。
Ehcache 从 Hibernate 发展而来,逐渐涵盖了 Cahce 界的全部功能,是目前发展势头最好的一个项目。具有快速,简单,低消耗,依赖性小,扩展性强,支持对象或序列化缓存,支持缓存或元素的失效,提供 LRU、LFU 和 FIFO 缓存策略,支持内存缓存和磁盘缓存,分布式缓存机制等等特点。
Cache 存储方式 :内存或磁盘。
官方网站:http://ehcache.sourceforge.net/
1. 快速.
2. 简单.
3. 多种缓存策略
4. 缓存数据有两级:内存和磁盘,因此无需担心容量问题
5. 缓存数据会在虚拟机重启的过程中写入磁盘
6. 可以通过 RMI、可插入 API 等方式进行分布式缓存
7. 具有缓存和缓存管理器的侦听接口
8. 支持多缓存管理器实例,以及一个实例的多个缓存区域
9. 提供 Hibernate 的缓存实现
Windows XP、JDK1.6.03、Tomcat5.5、EHcache2.1
注意:配置好环境变量。
ehcache-2.1.0-distribution.tar.gz:以及 ehcache-web-2.0.2-distribution.tar.gz
http://sourceforge.net/projects/ehcache/
注意:同时要下载源代码,部分功能需要修改源代码,重新做包。
Cache的配置很灵活,官方提供的Cache配置方式有好几种。你可以通过声明配置、在xml中配置、在程序里配置或者调用构造方法时传入不同的参数,具体的Cache的获取后续讲到。你可以将Cache的配置从代码中剥离出来,也可以在使用运行时配置,所谓的运行时配置无非也就是在代码中配置。以下是运行时配置的好处:
Ø 在同一个地方配置所有的Cache,这样很容易管理Cache的内存和磁盘消耗。
Ø 发布时可更改Cache配置。
Ø 可再安装阶段就检查出配置错误信息,而避免了运行时错误。
本文将会对ehcache.xml配置文件进行详细的阐述。如果你调用了CacheManager默认构造方法去创建CacheManager的实例,此方法会到classpath中找ehcache.xml文件,否则它会到类路径下找ehcache-failsafe.xml文件。而ehcache-failsafe.xml被包含在jar包中,所有它肯定能找的到。
ehcache-failsafe.xml提供了一个非常简单的默认配置,这样可以使用户在没有创建ehcache.xml的情况下使用Ehcache。不过这样做Ehcache会提醒用户创建一个正确的Ehcache配置。
ehcache.xml片段:
<ehcache>
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
</ehcache>
在Ehcache-1.6之前的版本,只支持ASCII编码的ehcache.xml配置文件。在Ehcach-1.6之后版本中,支持UTF8编码的ehcache.xml配置文件。因为向后兼容,所有采用ASCII编码的配置文件完全没有必要转换为UTF8。一个CacheManager必须要有一个XML配置。由于磁盘路径或是监听端口,多个CacheManager使用同一个配置文件时会出现错误。下面是ehcache.xml具体实例以及配置指南
<ehcache xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
l CacheManager配置
DmulticastGroupPort=4446,这样可以配置监听端口。
l DiskStore配置
如果你使用的DiskStore(磁盘缓存),你必须要配置DiskStore配置项。如果不配置,Ehcache将会使用java.io.tmpdir。diskStroe的“path”属性是用来配置磁盘缓存使用的物理路径的,Ehcache磁盘缓存使用的文件后缀名是.data和.index。
<disStore path=”java.io.tmpdir”/>
l CacheManagerEventListener配置
我们通过CacheManagerEventListenerFactory可以实例化一个CacheManagerPeerProvider,当我们从CacheManager中added和removed Cache时,将通知CacheManagerPeerProvider,这样一来,我们就可以很方面的对CacheManager中的Cache做一些统计。
注册到CacheManager的事件监听类名有: adding a Cache和removing a Cache <cacheManagerEventListenerFacotory class=”” properties=””/>
l CacheManagerPeerProvider配置
在集群中CacheManager配置CacheManagerPeerProviderFactory创建CacheManagerPeerProvider。具体的实例如下:
<cacheManagerPeerProviderFactoryclass="net.sf.ehcache.distribution. RMICacheManagerPeerProviderFactory"
properties="peerDiscovery=manual, rmiUrls=//server1:40000/sampleCache1|//server2:40000/sampleCache1| //server1:40000/sampleCache2|//server2:40000/sampleCache2"
propertySeparator="," />
l CacheManagerPeerListener配置
CacheManagerPeerListener配置是用来监听集群中缓存消息的分发的。
<cacheManagerPeerListenerFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
properties="hostName=fully_qualified_hostname_or_ip,
port=40001,
socketTimeoutMillis=120000"
propertySeparator="," />
l Cache配置
Ø name:Cache的唯一标识
Ø maxElementsInMemory:内存中最大缓存对象数。
Ø maxElementsOnDisk:磁盘中最大缓存对象数,若是0表示无穷大。
Ø eternal:Element是否永久有效,一但设置了,timeout将不起作用。
Ø overflowToDisk:配置此属性,当内存中Element数量达到maxElementsInMemory时,Ehcache将会Element写到磁盘中。
Ø timeToIdleSeconds:设置Element在失效前的允许闲置时间。仅当element不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
Ø timeToLiveSeconds:设置Element在失效前允许存活时间。最大时间介于创建时间和失效时间之间。仅当element不是永久有效时使用,默认是0.,也就是element存活时间无穷大。
Ø diskPersistent:是否缓存虚拟机重启期数据。(这个虚拟机是指什么虚拟机一直没看明白是什么,有高人还希望能指点一二)。
Ø diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
Ø diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
Ø memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。这里比较遗憾,Ehcache并没有提供一个用户定制策略的接口,仅仅支持三种指定策略,感觉做的不够理想。
Ø Cache Exception Handling配置
<cacheExceptionHandlerFactory class="com.example.ExampleExceptionHandlerFactory" properties="logLevel=FINE"/>
总结:
这里只对通用缓存的配置做了详细的阐述,至于RMI缓存和集群缓存可以参考这里。
下面给出几个配置示例:
Ø Ehcache默认Cache配置
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
diskSpoolBufferSizeMB="30"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
Ø SampleCache1配置
简单配置,在ehcache.xml文件中有此配置,在使用Ehcache前最好将其删除掉,自己配置。
缓存名sampleCache1,内存中最多可缓存10000个Element,其中的element会在闲置5分钟或是存活10分钟之后失效。
超过10000element时,element将会输出到磁盘中,输出路径是java.io.tmpdir。
<cache name="sampleCache1"
maxElementsInMemory="10000"
maxElementsOnDisk="1000"
eternal="false"
overflowToDisk="true"
diskSpoolBufferSizeMB="20"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LFU"
/>
Ø SampleCache2配置
Cache名为SampleCache2,内存中最多可以缓存1000个element,超出1000不能输出到磁盘中。缓存是永久有效的。
<cache name="sampleCache2"
maxElementsInMemory="1000"
eternal="true"
overflowToDisk="false"
memoryStoreEvictionPolicy="FIFO"
/>
Ø SampleCache3配置
Cache名为SampleCache3。可缓存到磁盘。磁盘缓存将会缓存虚拟机重启期的数据。磁盘缓存失效线程运行间隔时间是10分钟。
<cache name="sampleCache3"
maxElementsInMemory="500"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
diskPersistent="true"
diskExpiryThreadIntervalSeconds="1"
memoryStoreEvictionPolicy="LFU"
/>
Ø sampleDistributedCache1配置
Cache名为sampleDistributedCache1。
<cache name="sampleDistributedCache1"
maxElementsInMemory="10"
eternal="false"
timeToIdleSeconds="100"
timeToLiveSeconds="100"
overflowToDisk="false">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"/>
<bootstrapCacheLoaderFactory
class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory"/>
</cache>
Ø sampleDistributedCache2配置
<cache name="sampleDistributedCache2"
maxElementsInMemory="10"
eternal="false"
timeToIdleSeconds="100"
timeToLiveSeconds="100"
overflowToDisk="false">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicateAsynchronously=false, replicatePuts=false,
replicateUpdates=true, replicateUpdatesViaCopy=true,
replicateRemovals=false"/>
</cache>
Ø sampleDistributedCache3配置
<!--
Sample distributed cache named sampleDistributedCache3.
This cache replicates using defaults except that the asynchronous replication
interval is set to 200ms.
-->
<cache name="sampleDistributedCache3"
maxElementsInMemory="10"
eternal="false"
timeToIdleSeconds="100"
timeToLiveSeconds="100"
overflowToDisk="false">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="asynchronousReplicationIntervalMillis=200"/>
</cache>
主要为三层,最上层的是 CacheManager,他是操作 Ehcache 的入口。我们可以通过CacheManager.getInstance()获得一个单子的 CacheManger,或者通过 CacheManger 的构造函数创建一个新的 CacheManger。每个 CacheManager 都管理着多个 Cache。而每个Cache 都以一种类 Hash 的方式,关联着多个 Element。Element 则是我们用于存放要缓存内容的地方。
将 ehcache-2.1.0-distribution.tar.gz:以及 ehcache-web-2.0.2-distribution.tar.gz 解压得到需要将它们放置到 WEB-INF/lib 下。有一个重要的配置文件 ehcache.xml,可以从 ehcache 组件包中拷贝一个,也可以自己建立一个。需要放到classpath下。常放的路径为/WEB-INF/classes/ehcache.xml。
ehcach.xml 配置文件主要参数的解释,其实文件里有详细的英文注释//DiskStore 配置,
cache 文件的存放目录 ,主要的值有
² user.home - 用户主目录
² user.dir - 用户当前的工作目录
² java.io.tmpdir - Default temp file path 默认的 temp 文件目录
实例:
首先设置 EhCache,建立配置文件 ehcache.XML,默认的位置在 class- path,可以放
到你的 src 目录下:
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
<diskStore path="Java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000" <!- 缓存最大数目 ->
eternal="false" <!- 缓存是否持久 ->
overflowToDisk="true" <!- 当系统当机时,是否保存到磁盘 ->
timeToIdleSeconds="300" <!- 当缓存闲置 n 秒后销毁 ->
timeToLiveSeconds="180" <!- 当缓存存活 n 秒后销毁->
diskPersistent="false"
diskExpiryThreadIntervalSeconds= "120"/>
</ehcache>
了解 ehcache的几个概念,
Ø timeToIdleSeconds,多长时间不访问该缓存,那么 ehcache就会清除该缓存。
Ø timeToLiveSeconds,缓存的存活时间,从开始创建的时间算起。
Ehcache 的三种清空策略:
1. FIFO,first in first out,这个是大家最熟的,先进先出。
2. LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个 hit 属性,hit 值最小的将会被清出缓存。
3. LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
首页的页面缓存:
一个网站的首页估计是被访问的次数最多的,我们可以考虑给首页做一个页面缓存。
缓存策略:我认为应该是某个固定时间之内不变的,比如说 2 分钟更新一次,以应用
结构 page-filter-action-service-dao-db 为例。
位置:页面缓存做到尽量靠近客户的地方,就是在 page 和 filter 之间 ,这样的优
点就是第一个用户请求之后,页面被缓存,第二个用户再来请求的时候,走到 filter 这
个请求就结束了,无需再走后面的 action- service-dao-db。带来的好处是服务器压力的减低和客户段页面响应速度的加快。
首页的页面缓存的存活时间,我们定的是 2 分钟,那么也就是说我们的timeToLiveSeconds 应该设置为 120 ,同时我们的 timeToIdleSeconds 最好也设置为 2 分钟,或者小于 2 分钟。
我们来看一下下面这个配置,这个配置片段应该放到 ehcache.xml中:SimplePageCachingFilter 是缓存的名字,maxElementsInMemory 表示内存中SimplePageCachingFilter 缓存中元素的最大数量为 10,maxElementsOnDisk 是指持久化
该缓存的元素到硬盘上的最大数量也为 10,eternal=false 意味着该缓存会死亡。overflowToDisk=true 意思是表示当缓存中元素的数量超过限制时,就把这些元素持久化到硬盘,如果 overflowToDisk 是 false ,那么 maxElementsOnDisk 的设置就没有什么意义了。memoryStoreEvictionPolicy=LFU 是指按照缓存的 hit 值来清除,也就是说缓存满了之后,新的对象需要缓存时,将会将缓存中 hit 值最小的对象清除出缓存,给新的对象腾出地方来了。
接着我们来看一下 SimplePageCachingFilter 的配置,
XML/HTML 代码
<filter>
<filter-name>indexCacheFilterfilter-name>
<filter-class>
net.sf.ehcache.constructs.web.filter.SimplePageCachingFilter
<filter-class>
<filter>
<filter-mapping>
<filter-name>indexCacheFilterfilter-name>
<url-pattern>*index.actionurl-pattern>
<filter-mapping>
就只需要这么多步骤,我们就可以给某个页面做一个缓存的,把上面这段配置放到你的
web.xml 中,那么当你打开首页的时候,你会发现,2 分钟才会有一堆 sql 语句出现在控
制台上。当然你也可以调成 5 分钟,总之一切都在控制中。
好了,缓存整个页面看上去是非常的简单,甚至都不需要写一行代码,只需要几行配置
就行了,够简单吧,虽然看上去简单,但是事实上内部实现却不简单哦,有兴趣的话,
大家可以看看 SimplePageCachingFilter 继承体系的源代码。
上面的配置针对的情况是缓存首页的全部,如果你只想缓存首页的部分内容时,你需要
使用 SimplePageFragmentCachingFilter 这个 filter。我们看一下如下片断:
XML/HTML 代码
<filter>
<filter-name>indexCacheFilterfilter-name>
<filter-class>
net.sf.ehcache.constructs.web.filter.SimplePageFragmentCachingFilter
<filter-class>
filter>
<filter-mapping>
<filter-name>indexCacheFilter</filter-name>
<url-pattern>*/index_right.jsp</url-pattern>
<filter-mapping>
这个 jsp 需要被 jsp:include 到其他页面,这样就做到局部页面的缓存。这一点貌似没有 oscache 的 tag 好用。事实上在 cachefilter 中还有一个特性,就是 gzip,也就是说缓存中的元素是被压缩过的,如果客户浏览器支持压缩的话,filter 会直接返回压缩过的流,这样节省了带宽,把解压的工作交给了客户浏览器,如果客户的浏览器不支持 gzip,那么 filter 会把缓存的元素拿出来解压后再返回给客户浏览器(大多数爬虫是不支持 gzip 的,所以 filter 也会解压后再返回流),这样做的优点是节省带宽,缺点就是增加了客户浏览器的负担(但是我觉得对当代的计算机而言,这个负担微乎其微)。好了,如果你的页面正好也需要用到页面缓存,不防可以考虑一下 ehcache,因为它实在是非常简单,而且易用。
总结:ehcache 是一个非常轻量级的缓存实现,而且从 1.2 之后就支持了集群,目前的最新版本是 1.3,而且是 hibernate 默认的缓存 provider。虽然本文是介绍的是 ehcache 对页面缓存的支持,但是 ehcache 的功能远不止如此,当然要使用好缓存,对 JEE 中缓存的原理,使用范围,适用场景等等都需要有比较深刻的理解,这样才能用好缓存,用对缓存。
最后复习一下 ehcache 中缓存的 3 种清空策略:
1. FIFO,first in first out,这个是大家最熟的,先进先出,不多讲了
2. LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个 hit 属性,hit 值最小的将会被清出缓存。
3. LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
1. 创建 CacheManager (net.sf.ehcache.CacheManager)
1) 使用默认配置文件创建
CacheManager manager = CacheManager.create();
2) 使用指定配置文件创建
CacheManager manager = CacheManager.create("src/config/ehcache.xml");
3) 从 classpath 找寻配置文件并创建
URL url = getClass().getResource("/anothername.xml");
CacheManager manager = CacheManager.create(url);
4) 通过输入流创建
InputStream fis = new FileInputStream(new
File("src/config/ehcache.xml").getAbsolutePath());
try { manager = CacheManager.create(fis); } finally { fis.close(); }
2. 创建 Caches (net.sf.ehcache.Cache)
1) 取得配置文件中预先定义的 sampleCache1 设置,生成一个 Cache
Cache cache = manager.getCache("sampleCache1");
2) 设置一个名为 test 的新 cache,test 属性为默认
CacheManager manager = CacheManager.create();
manager.addCache("test");
3) 设置一个名为 test 的新 cache,并定义其属性
CacheManager manager = CacheManager.create();
Cache cache = new Cache("test", 1, true, false, 5, 2);
manager.addCache(cache);
4) 删除 cache
CacheManager singletonManager = CacheManager.create();
singletonManager.removeCache("sampleCache1");
3. 使用 Caches
1) 往 cache 中加入元素
Element element = new Element("key1", "value1");
cache.put(new Element(element);
2) 从 cache 中取得元素
Element element = cache.get("key1");
3) 从 cache 中删除元素
Cache cache = manager.getCache("sampleCache1");
Element element = new Element("key1", "value1");
cache.remove("key1");
4. 卸载 CacheManager ,关闭 Cache
manager.shutdown();
需要使用 Spring 来实现一个 Cache 简单的解决方案,具体需求如下:使用任意一个现有开源 Cache Framework,要求使用 Cache 系统中 Service 或则 DAO 层的 get/find 等方法返回结果,如果数据更新(使用 Create/update/delete 方法),则刷新 cache 中相应的内容。根据需求,计划使用 Spring AOP + ehCache 来实现这个功能,采用 ehCache 原因之一是Spring 提供了 ehCache 的支持,至于为何仅仅支持 ehCache 而不支持 osCache 和JBossCache 无从得知(Hibernate???),但毕竟 Spring 提供了支持,可以减少一部分工作量:)。二是后来实现了 OSCache 和 JBoss Cache 的方式后,经过简单测试发现几个 Cache 在效率上没有太大的区别(不考虑集群),决定采用 ehCahce。AOP 嘛,少不了拦截器,先创建一个实现了 MethodInterceptor 接口的拦截器,用来拦截Service/DAO 的方法调用,拦截到方法后,搜索该方法的结果在 cache 中是否存在,如果存在,返回 cache 中的缓存结果,如果不存在,返回查询数据库的结果,并将结果缓存到 cache 中。
MethodCacheInterceptor.java
Java 代码
package com.co.cache.ehcache;
import java.io.Serializable;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
public class MethodCacheInterceptor implements MethodInterceptor, InitializingBean
{
private static final Log logger = LogFactory.getLog(MethodCacheInterceptor.class);
private Cache cache;
public void setCache(Cache cache) {
this.cache = cache;
}
public MethodCacheInterceptor() {
super();
}
/**
* 拦截 Service/DAO 的方法,并查找该结果是否存在,如果存在就返回 cache 中的值,
* 否则,返回数据库查询结果,并将查询结果放入 cache
*/
public Object invoke(MethodInvocation invocation) throws Throwable {
String targetName = invocation.getThis().getClass().getName();
String methodName = invocation.getMethod().getName();
Object[] arguments = invocation.getArguments();
Object result;
logger.debug("Find object from cache is " + cache.getName());
String cacheKey = getCacheKey(targetName, methodName, arguments);
Element element = cache.get(cacheKey);
if (element == null) {
logger.debug("Hold up method , Get method result and create cache........!");
result = invocation.proceed();
element = new Element(cacheKey, (Serializable) result);
cache.put(element);
}
return element.getValue();
}
/**
* 获得 cache key 的方法,cache key 是 Cache 中一个 Element 的唯一标识
* cache key 包括 包名+类名+方法名,如 com.co.cache.service.UserServiceImpl.getAllUser
*/
private String getCacheKey(String targetName, String methodName, Object[] arguments) {
StringBuffer sb = new StringBuffer();
sb.append(targetName).append(".").append(methodName);
if ((arguments != null) && (arguments.length != 0)) {
for (int i = 0; i < arguments.length; i++) {
sb.append(".").append(arguments[i]);
}
}
return sb.toString();
}
/**
* implement InitializingBean,检查 cache 是否为空
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(cache, "Need a cache. Please use setCache(Cache) create it.");
}
}
上面的代码中可以看到,在方法 public Object invoke(MethodInvocation invocation) 中,完成了搜索 Cache/新建 cache 的功能。
Element element = cache.get(cacheKey);
这句代码的作用是获取 cache 中的 element,如果 cacheKey 所对应的 element 不存在,将会返回一个 null 值。
Java 代码
result = invocation.proceed();
这句代码的作用是获取所拦截方法的返回值,详细请查阅 AOP 相关文档。随后,再建立一个拦截器 MethodCacheAfterAdvice,作用是在用户进行 create/update/delete操作时来刷新/remove 相关 cache 内容,这个拦截器实现了 AfterReturningAdvice 接口,将会在所拦截的方法执行后执行在 public void afterReturning(Object arg0, Method arg1,Object[] arg2, Object arg3)方法中所预定的操作
Java 代码
package com.co.cache.ehcache;
import java.lang.reflect.Method;
import java.util.List;
import net.sf.ehcache.Cache;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
public class MethodCacheAfterAdvice implements AfterReturningAdvice, InitializingBean
{
private static final Log logger = LogFactory.getLog(MethodCacheAfterAdvice.class);
private Cache cache;
public void setCache(Cache cache) {
this.cache = cache;
}
public MethodCacheAfterAdvice() {
super();
}
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws
Throwable {
String className = arg3.getClass().getName();
List list = cache.getKeys();
for(int i = 0;i<list.size();i++){
String cacheKey = String.valueOf(list.get(i));
if(cacheKey.startsWith(className)){
cache.remove(cacheKey);
logger.debug("remove cache " + cacheKey);
}
}
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(cache, "Need a cache. Please use setCache(Cache) create it.");
}
}
上面的代码很简单,实现了 afterReturning 方法实现自 AfterReturningAdvice 接口,方法中所定义的内容将会在目标方法执行后执行,在该方法中的作用是获取目标 class 的全名,如:com.co.cache.test.TestServiceImpl,然后循环 cache 的 key list,remove cache 中所有和该 class 相关的 element。
Java 代码
String className = arg3.getClass().getName();
随后,开始配置 ehCache 的属性,ehCache 需要一个 xml 文件来设置 ehCache 相关的一些属性,如最大缓存数量、cache 刷新的时间等等.
ehcache.xml
<ehcache>
<diskStore path="c:\\myapp\\cache"/>
<defaultCache
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
<cache name="DEFAULT_CACHE"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300000"
timeToLiveSeconds="600000"
overflowToDisk="true"
/>
</ehcache>
配置每一项的详细作用不再详细解释,有兴趣的请 google 下 ,这里需要注意一点defaultCache 标签定义了一个默认的 Cache,这个 Cache 是不能删除的,否则会抛出 No default cache is configured 异常。另外,由于使用拦截器来刷新 Cache 内容,因此在定义cache 生命周期时可以定义较大的数值,timeToIdleSeconds="300000",timeToLiveSeconds="600000",好像还不够大?然后,在将 Cache 和两个拦截器配置到 Spring,这里没有使用 2.0 里面 AOP 的标签。
cacheContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- 引用 ehCache 的配置 -->
<bean id="defaultCacheManager"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation">
<value>ehcache.xml</value>
</property>
</bean>
<!-- 定义 ehCache 的工厂,并设置所使用的 Cache name -->
<bean id="ehCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager">
<ref local="defaultCacheManager"/>
</property>
<property name="cacheName">
<value>DEFAULT_CACHE</value>
</property>
</bean>
<!-- find/create cache 拦截器 -->
<bean id="methodCacheInterceptor"
class="com.co.cache.ehcache.MethodCacheInterceptor">
<property name="cache">
<ref local="ehCache" />
</property>
</bean>
<!-- flush cache 拦截器 -->
<bean id="methodCacheAfterAdvice"
class="com.co.cache.ehcache.MethodCacheAfterAdvice">
<property name="cache">
<ref local="ehCache" />
</property>
</bean>
<bean id="methodCachePointCut"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="methodCacheInterceptor"/>
</property>
<property name="patterns">
<list>
<value>.*find.*</value>
<value>.*get.*</value>
</list>
</property>
</bean>
<bean id="methodCachePointCutAdvice"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="methodCacheAfterAdvice"/>
</property>
<property name="patterns">
<list>
<value>.*create.*</value>
<value>.*update.*</value>
<value>.*delete.*</value>
</list>
</property>
</bean>
</beans>
上面的代码最终创建了两个"切入点",methodCachePointCut 和methodCachePointCutAdvice,分别用于拦截不同方法名的方法,可以根据需要任意增加所需要拦截方法的名称。需要注意的是Java 代码
<bean id="ehCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager">
<ref local="defaultCacheManager"/>
</property>
<property name="cacheName">
<value>DEFAULT_CACHE</value>
</property>
</bean>
如果 cacheName 属性内设置的 name 在 ehCache.xml 中无法找到,那么将使用默认的
cache(defaultCache 标签定义),事实上到了这里,一个简单的 Spring + ehCache Framework 基本完成了,为了测试效果,举一个实际应用的例子,定义一个 TestService 和它的实现类 TestServiceImpl,里面包含两个方法 getAllObject()和 updateObject(Object Object),具体代码如下:
TestService.java
Java 代码
package com.co.cache.test;
import java.util.List;
public interface TestService {
public List getAllObject();
public void updateObject(Object Object);
}
TestServiceImpl.java
Java 代码
package com.co.cache.test;
import java.util.List;
public class TestServiceImpl implements TestService
{
public List getAllObject() {
System.out.println("---TestService:Cache 内不存在该 element,查找并放入 Cache!");
return null;
}
public void updateObject(Object Object) {
System.out.println("---TestService:更新了对象,这个 Class 产生的 cache 都将被 remove!
");
}
}
使用 Spring 提供的 AOP 进行配置
applicationContext.xml
XML/HTML 代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<import resource="cacheContext.xml"/>
<bean id="testServiceTarget" class="com.co.cache.test.TestServiceImpl"/>
<bean id="testService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref local="testServiceTarget"/>
</property>
<property name="interceptorNames">
<list>
<value>methodCachePointCut</value>
<value>methodCachePointCutAdvice</value>
</list>
</property>
</bean>
</beans>
这里一定不能忘记 import cacheContext.xml 文件,不然定义的两个拦截器就没办法使用
了。
最后,写一个测试的代码
MainTest.java
Java 代码
(DEFAULT_CONTEXT_FILE);
TestService testService = (TestService)context.getBean("testService");
System.out.println("1--第一次查找并创建 cache");
testService.getAllObject();
System.out.println("2--在 cache 中查找");
testService.getAllObject();
System.out.println("3--remove cache");
testService.updateObject(null);
System.out.println("4--需要重新查找并创建 cache");
testService.getAllObject();
}
}
运行,结果如下
Java 代码
1--第一次查找并创建 cache
---TestService:Cache 内不存在该 element,查找并放入 Cache!
2--在 cache 中查找
3--remove cache
---TestService:更新了对象,这个 Class 产生的 cache 都将被 remove!
4--需要重新查找并创建 cache
---TestService:Cache 内不存在该 element,查找并放入 Cache!
大功告成 .可以看到,第一步执行 getAllObject(),执行 TestServiceImpl 内的方法,并创建了 cache,在第二次执行 getAllObject()方法时,由于 cache 有该方法的缓存,直接从cache 中 get 出方法的结果,所以没有打印出 TestServiceImpl 中的内容,而第三步,调用了 updateObject 方法,和 TestServiceImpl 相关的 cache 被 remove,所以在第四步执行时,又执行 TestServiceImpl 中的方法,创建 Cache。网上也有不少类似的例子,但是很多都不是很完备,自己参考了一些例子的代码,其实在 spring-modules 中也提供了对几种 cache 的支持,ehCache,OSCache,JBossCache 这些,看了一下,基本上都是采用类似的方式,只不过封装的更完善一些,主要思路也还是 Spring 的 AOP,有兴趣的可以研究一下。
ehcache 提供三种网络连接策略来实现集群,rmi、jgroup 还有 jms。这里只说 rmi方式。同时 ehcache 可以实现多播的方式实现集群。也可以手动指定集群主机序列实现集群,本例应用手动指定。这里说点题外话,本来看着分发包中的原来的例子配置是一件不怎么难的事情,应该很容易就能实现。但是一开始,我是在我的 linux 主机上和我的主操作系统 windows 上实现集群配置。结果反过来弄过去,都没有成功。然后在网上找一些别人的配置经验,竟然都是配置片段,没有完整的实例文件。结果配置半天没成功。但我怀疑是我的 linux系统有些地方可能没有配置好,于是先不管他。又开启了我的另一个 windows 主机。然后把程序部署上去,竟然一次试验成功。高兴的同时,我得发句话“不要把代码片段称作实例,这很不负责任”。同时还存在一个问题,在 linux下没有部署成功的原因有待查明。具体说明:配置 cacheManagerPeerListenerFactory 是配宿主主机配置监听程序,来发现其他主机发来的同步请求配置 cacheManagerPeerProviderFactory 是指定除自身之外的网络群体中其他提供同步的主机列表,用“|”分开不同的主机。
下面的例子的测试过程是:主机 B 缓存开启,并从名为 UserCache 的缓存中循环抓取键值为“key1”的元素,直到取到,才退出循环。主机 A 缓存启动,并在名为 UserCache 的缓存中放入键值为“key1”的元素。显然,如果主机 B 取到的元素,那么就证明同步成功,也就是集群成功。所以在测试过程中先启动主机 B 的测试程序,在启动主机 A 的测试程序。
下面具体说配置文件以及测试程序:
1. 主机 A 的配置文件以及测试源代码
config/ehcache_cluster.xml
Xml 代码
<ehcachexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance
xsi:noNamespaceSchemaLocation="ehcache.xsd">
<cacheManagerPeerProviderFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
properties="peerDiscovery=manual,
rmiUrls=//192.168.1.254:40000/UserCache"/>
<cacheManagerPeerListenerFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
properties="hostName=192.168.1.126,port=40000,socketTimeoutMillis=120000" />
<defaultCache maxElementsInMemory="10000"eternal="false"
timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true"
diskSpoolBufferSizeMB="30"maxElementsOnDisk="10000000"
diskPersistent="false"diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" />
</defaultCache>
<cache name="UserCache"maxElementsInMemory="1000"eternal="false"
timeToIdleSeconds="100000"timeToLiveSeconds="100000"
overflowToDisk="false">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" />
</cache>
</ehcache>
tutorial/UsingCacheCluster
Java 代码
package tutorial;
import java.net.URL;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
public class UsingCacheCluster {
public static void main(String[] args) throws Exception
{
URL url = UsingCacheCluster.class.getClassLoader().getResource(
"config/ehcache_cluster.xml");
CacheManager manager = new CacheManager(url);
//取得 Cache
Cache cache = manager.getCache("UserCache");
Element element = new Element("key1", "value1");
cache.put(element);
Element element1 = cache.get("key1");
System.out.println(element1.getValue());
}
}
2. 主机 B 上的配置文件以及测试代码
config/ehcache_cluster.xml
Xml 代码
<ehcachexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd">
<cacheManagerPeerProviderFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
properties="peerDiscovery=manual,rmiUrls=//192.168.1.126:40000/UserCache"/>
<cacheManagerPeerListenerFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
properties="hostName=192.168.1.254,port=40000, socketTimeoutMillis=120000" />
<defaultCache maxElementsInMemory="10000"eternal="false"
timeToIdleSeconds="120"timeToLiveSeconds="120"overflowToDisk="true"
diskSpoolBufferSizeMB="30"maxElementsOnDisk="10000000"
diskPersistent="false"diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" />
</defaultCache>
<cache name="UserCache"maxElementsInMemory="1000"eternal="false"
timeToIdleSeconds="100000"timeToLiveSeconds="100000"
overflowToDisk="false">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" />
</cache>
</ehcache>
tutorial/UsingCacheCluster
Java 代码
package tutorial;
import java.net.URL;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
public class UsingCacheCluster {
public static void main(String[] args) throws Exception
{
URL url = UsingCacheCluster.class.getClassLoader().getResource("config/ehcache_cluster.xml");
CacheManager manager = new CacheManager(url);
//取得 Cache
Cache cache = manager.getCache("UserCache");
while(true) {
Element e = cache.get("key1");
if(e != null) {
System.out.println(e.getValue());
break;
}
Thread.sleep(1000);
}
}
}