Spring整合Ehcache管理缓存
HilarioDown
8年前
<h2><strong>前言</strong></h2> <p>Ehcache 是一个成熟的缓存框架,你可以直接使用它来管理你的缓存。</p> <p>Spring 提供了对缓存功能的抽象:即允许绑定不同的缓存解决方案(如Ehcache),但本身不直接提供缓存功能的实现。它支持注解方式使用缓存,非常方便。</p> <p>本文先通过Ehcache独立应用的范例来介绍它的基本使用方法,然后再介绍与Spring整合的方法。</p> <h2><strong>概述</strong></h2> <p>Ehcache是什么?</p> <p>EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点。它是Hibernate中的默认缓存框架。</p> <p>Ehcache已经发布了3.1版本。但是本文的讲解基于2.10.2版本。</p> <p>为什么不使用最新版呢?因为Spring4还不能直接整合Ehcache 3.x。虽然可以通过JCache间接整合,Ehcache也支持JCache,但是个人觉得不是很方便。</p> <h2><strong>安装</strong></h2> <p>Ehcache</p> <p>如果你的项目使用maven管理,添加以下依赖到你的 <em>pom.xml</em> 中。</p> <pre> <code class="language-java"><dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.10.2</version> <type>pom</type> </dependency></code></pre> <p>如果你的项目不使用maven管理,请在 <a href="/misc/goto?guid=4959721579495269536" rel="nofollow,noindex">Ehcache官网下载地址</a> 下载jar包。</p> <p>Spring</p> <p>如果你的项目使用maven管理,添加以下依赖到你的 <em>pom.xml</em> 中。</p> <p>spring-context-support 这个jar包中含有Spring对于缓存功能的抽象封装接口。</p> <pre> <code class="language-java"><dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>4.1.4.RELEASE</version> </dependency></code></pre> <h2><strong>Ehcache的使用</strong></h2> <h2><strong>HelloWorld范例</strong></h2> <p>接触一种技术最快最直接的途径总是一个Hello World例子,毕竟动手实践印象更深刻,不是吗?</p> <p>(1) 在classpath下添加 <em>ehcache.xml</em></p> <p>添加一个名为 <em>helloworld</em> 的缓存。</p> <pre> <code class="language-java"><?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"> <!-- 磁盘缓存位置 --> <diskStore path="java.io.tmpdir/ehcache"/> <!-- 默认缓存 --> <defaultCache maxEntriesLocalHeap="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" maxEntriesLocalDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"/> <!-- helloworld缓存 --> <cache name="helloworld" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="5" timeToLiveSeconds="5" overflowToDisk="false" memoryStoreEvictionPolicy="LRU"/> </ehcache></code></pre> <p>(2) <em>EhcacheDemo.java</em></p> <p>Ehcache会自动加载classpath根目录下名为 <em>ehcache.xml</em> 文件。</p> <p>EhcacheDemo的工作步骤如下:</p> <p>在EhcacheDemo中,我们引用 <em>ehcache.xml</em> 声明的名为 <em>helloworld</em> 的缓存来创建 Cache 对象;</p> <p>然后我们用一个键值对来实例化 Element 对象;</p> <p>将 Element 对象添加到 Cache ;</p> <p>然后用 Cache 的get方法获取 Element 对象。</p> <pre> <code class="language-java">public class EhcacheDemo { public static void main(String[] args) throws Exception { // Create a cache manager final CacheManager cacheManager = new CacheManager(); // create the cache called "helloworld" final Cache cache = cacheManager.getCache("helloworld"); // create a key to map the data to final String key = "greeting"; // Create a data element final Element putGreeting = new Element(key, "Hello, World!"); // Put the element into the data store cache.put(putGreeting); // Retrieve the data element final Element getGreeting = cache.get(key); // Print the value System.out.println(getGreeting.getObjectValue()); } }</code></pre> <p>输出</p> <pre> <code class="language-java">Hello, World!</code></pre> <h2><strong>Ehcache基本操作</strong></h2> <p>Element 、 Cache 、 CacheManager 是Ehcache最重要的API。</p> <ul> <li>Element:缓存的元素,它维护着一个键值对。</li> <li>Cache:它是Ehcache的核心类,它有多个 Element ,并被 CacheManager 管理。它实现了对缓存的逻辑行为。</li> <li> <p>CacheManager: Cache 的容器对象,并管理着 Cache 的生命周期。</p> </li> </ul> <h3> </h3> <p> </p> <p><strong>创建CacheManager</strong></p> <p>下面的代码列举了创建 CacheManager 的五种方式。</p> <p>使用静态方法 create() 会以默认配置来创建单例的 CacheManager 实例。</p> <p>newInstance() 方法是一个工厂方法,以默认配置创建一个新的 CacheManager 实例。</p> <p>此外, newInstance() 还有几个重载函数,分别可以通过传入 String 、 URL 、 InputStream 参数来加载配置文件,然后创建 CacheManager 实例。</p> <pre> <code class="language-java">// 使用Ehcache默认配置获取单例的CacheManager实例 CacheManager.create(); String[] cacheNames = CacheManager.getInstance().getCacheNames(); // 使用Ehcache默认配置新建一个CacheManager实例 CacheManager.newInstance(); String[] cacheNames = manager.getCacheNames(); // 使用不同的配置文件分别创建一个CacheManager实例 CacheManager manager1 = CacheManager.newInstance("src/config/ehcache1.xml"); CacheManager manager2 = CacheManager.newInstance("src/config/ehcache2.xml"); String[] cacheNamesForManager1 = manager1.getCacheNames(); String[] cacheNamesForManager2 = manager2.getCacheNames(); // 基于classpath下的配置文件创建CacheManager实例 URL url = getClass().getResource("/anotherconfigurationname.xml"); CacheManager manager = CacheManager.newInstance(url); // 基于文件流得到配置文件,并创建CacheManager实例 InputStream fis = new FileInputStream(new File ("src/config/ehcache.xml").getAbsolutePath()); try { CacheManager manager = CacheManager.newInstance(fis); } finally { fis.close(); }</code></pre> <h3><strong>添加缓存</strong></h3> <p>需要强调一点, Cache 对象在用 addCache 方法添加到 CacheManager 之前,是无效的。</p> <p>使用CacheManager的addCache方法可以根据缓存名将ehcache.xml中声明的cache添加到容器中;它也可以直接将Cache对象添加到缓存容器中。</p> <p>Cache 有多个构造函数,提供了不同方式去加载缓存的配置参数。</p> <p>有时候,你可能需要使用API来动态的添加缓存,下面的例子就提供了这样的范例。</p> <pre> <code class="language-java">// 除了可以使用xml文件中配置的缓存,你也可以使用API动态增删缓存 // 添加缓存 manager.addCache(cacheName); // 使用默认配置添加缓存 CacheManager singletonManager = CacheManager.create(); singletonManager.addCache("testCache"); Cache test = singletonManager.getCache("testCache"); // 使用自定义配置添加缓存,注意缓存未添加进CacheManager之前并不可用 CacheManager singletonManager = CacheManager.create(); Cache memoryOnlyCache = new Cache("testCache", 5000, false, false, 5, 2); singletonManager.addCache(memoryOnlyCache); Cache test = singletonManager.getCache("testCache"); // 使用特定的配置添加缓存 CacheManager manager = CacheManager.create(); Cache testCache = new Cache( new CacheConfiguration("testCache", maxEntriesLocalHeap) .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU) .eternal(false) .timeToLiveSeconds(60) .timeToIdleSeconds(30) .diskExpiryThreadIntervalSeconds(0) .persistence(new PersistenceConfiguration().strategy(Strategy.LOCALTEMPSWAP))); manager.addCache(testCache);</code></pre> <h2><strong>删除缓存</strong></h2> <p>删除缓存比较简单,你只需要将指定的缓存名传入 removeCache 方法即可。</p> <pre> <code class="language-java">CacheManager singletonManager = CacheManager.create(); singletonManager.removeCache("sampleCache1");</code></pre> <h2><strong>实现基本缓存操作</strong></h2> <p>Cache最重要的两个方法就是put和get,分别用来添加Element和获取Element。</p> <p>Cache还提供了一系列的get、set方法来设置或获取缓存参数,这里不一一列举,更多API操作可参考 <a href="/misc/goto?guid=4959721579588117060" rel="nofollow,noindex">官方API开发手册</a> 。</p> <pre> <code class="language-java">/** * 测试:使用默认配置或使用指定配置来创建CacheManager * * @author victor zhang */ public class CacheOperationTest { private final Logger log = LoggerFactory.getLogger(CacheOperationTest.class); /** * 使用Ehcache默认配置(classpath下的ehcache.xml)获取单例的CacheManager实例 */ @Test public void operation() { CacheManager manager = CacheManager.newInstance("src/test/resources/ehcache/ehcache.xml"); // 获得Cache的引用 Cache cache = manager.getCache("userCache"); // 将一个Element添加到Cache cache.put(new Element("key1", "value1")); // 获取Element,Element类支持序列化,所以下面两种方法都可以用 Element element1 = cache.get("key1"); // 获取非序列化的值 log.debug("key:{}, value:{}", element1.getObjectKey(), element1.getObjectValue()); // 获取序列化的值 log.debug("key:{}, value:{}", element1.getKey(), element1.getValue()); // 更新Cache中的Element cache.put(new Element("key1", "value2")); Element element2 = cache.get("key1"); log.debug("key:{}, value:{}", element2.getObjectKey(), element2.getObjectValue()); // 获取Cache的元素数 log.debug("cache size:{}", cache.getSize()); // 获取MemoryStore的元素数 log.debug("MemoryStoreSize:{}", cache.getMemoryStoreSize()); // 获取DiskStore的元素数 log.debug("DiskStoreSize:{}", cache.getDiskStoreSize()); // 移除Element cache.remove("key1"); log.debug("cache size:{}", cache.getSize()); // 关闭当前CacheManager对象 manager.shutdown(); // 关闭CacheManager单例实例 CacheManager.getInstance().shutdown(); } }</code></pre> <h2><strong>缓存配置</strong></h2> <p>Ehcache支持通过xml文件和API两种方式进行配置。</p> <h3><strong>xml方式</strong></h3> <p>Ehcache的 CacheManager 构造函数或工厂方法被调用时,会默认加载classpath下名为 <em>ehcache.xml</em> 的配置文件。如果加载失败,会加载Ehcache jar包中的 <em>ehcache-failsafe.xml</em> 文件,这个文件中含有简单的默认配置。</p> <p>ehcache.xml配置参数说明:</p> <ul> <li><strong>name</strong> :缓存名称。</li> <li><strong>maxElementsInMemory</strong> :缓存最大个数。</li> <li><strong>eternal</strong> :缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。</li> <li><strong>timeToIdleSeconds</strong> :置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。</li> <li><strong>timeToLiveSeconds</strong> :缓存数据的生存时间(TTL),也就是一个元素从构建到消亡的最大时间间隔值,这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。</li> <li><strong>maxEntriesLocalDisk</strong> :当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。</li> <li><strong>overflowToDisk</strong> :内存不足时,是否启用磁盘缓存。</li> <li><strong>diskSpoolBufferSizeMB</strong> :这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。</li> <li><strong>maxElementsOnDisk</strong> :硬盘最大缓存个数。</li> <li><strong>diskPersistent</strong> :是否在VM重启时存储硬盘的缓存数据。默认值是false。</li> <li><strong>diskExpiryThreadIntervalSeconds</strong> :磁盘失效线程运行时间间隔,默认是120秒。</li> <li><strong>memoryStoreEvictionPolicy</strong> :当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。</li> <li><strong>clearOnFlush</strong> :内存数量最大时是否清除。</li> </ul> <p>ehcache.xml的一个范例</p> <pre> <code class="language-java"><?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"> <!-- 磁盘缓存位置 --> <diskStore path="java.io.tmpdir/ehcache"/> <!-- 默认缓存 --> <defaultCache maxEntriesLocalHeap="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" maxEntriesLocalDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <persistence strategy="localTempSwap"/> </defaultCache> <cache name="userCache" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="3" timeToLiveSeconds="3" maxEntriesLocalDisk="10000000" overflowToDisk="false" memoryStoreEvictionPolicy="LRU"/> </ehcache></code></pre> <h3><strong>API方式</strong></h3> <p>xml配置的参数也可以直接通过编程方式来动态的进行配置(dynamicConfig没有设为false)。</p> <pre> <code class="language-java">Cache cache = manager.getCache("sampleCache"); CacheConfiguration config = cache.getCacheConfiguration(); config.setTimeToIdleSeconds(60); config.setTimeToLiveSeconds(120); config.setmaxEntriesLocalHeap(10000); config.setmaxEntriesLocalDisk(1000000);</code></pre> <p>也可以通过 disableDynamicFeatures() 方式关闭动态配置开关。配置以后你将无法再以编程方式配置参数。</p> <pre> <code class="language-java">Cache cache = manager.getCache("sampleCache"); cache.disableDynamicFeatures();</code></pre> <h2><strong>Spring整合Ehcache</strong></h2> <p>Spring3.1开始添加了对缓存的支持。和事务功能的支持方式类似,缓存抽象允许底层使用不同的缓存解决方案来进行整合。</p> <p>Spring4.1开始支持JSR-107注解。</p> <p><em>注:我本人使用的Spring版本为4.1.4.RELEASE,目前Spring版本仅支持Ehcache2.5以上版本,但不支持Ehcache3。</em></p> <h2><strong>绑定Ehcache</strong></h2> <p>org.springframework.cache.ehcache.EhCacheManagerFactoryBean 这个类的作用是加载Ehcache配置文件。</p> <p>org.springframework.cache.ehcache.EhCacheCacheManager 这个类的作用是支持net.sf.ehcache.CacheManager。</p> <p><em>spring-ehcache.xml</em> 的配置</p> <pre> <code class="language-java"><?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.2.xsd"> <description>ehcache缓存配置管理文件</description> <bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"> <property name="configLocation" value="classpath:ehcache/ehcache.xml"/> </bean> <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"> <property name="cacheManager" ref="ehcache"/> </bean> <!-- 启用缓存注解开关 --> <cache:annotation-driven cache-manager="cacheManager"/> </beans></code></pre> <h2><strong>使用Spring的缓存注解</strong></h2> <h3><strong>开启注解</strong></h3> <p>Spring为缓存功能提供了注解功能,但是你必须启动注解。</p> <p>你有两个选择:</p> <p>(1) 在xml中声明</p> <p>像上一节spring-ehcache.xml中的做法一样,使用 <cache:annotation-driven/></p> <pre> <code class="language-java"><cache:annotation-driven cache-manager="cacheManager"/></code></pre> <p>(2) 使用标记注解</p> <p>你也可以通过对一个类进行注解修饰的方式在这个类中使用缓存注解。</p> <p>范例如下:</p> <pre> <code class="language-java">@Configuration @EnableCaching public class AppConfig { }</code></pre> <h2><strong>注解基本使用方法</strong></h2> <p>Spring对缓存的支持类似于对事务的支持。</p> <p>首先使用注解标记方法,相当于定义了切点,然后使用Aop技术在这个方法的调用前、调用后获取方法的入参和返回值,进而实现了缓存的逻辑。</p> <p>下面三个注解都是方法级别:</p> <h3><strong>@Cacheable</strong></h3> <p>表明所修饰的方法是可以缓存的:当第一次调用这个方法时,它的结果会被缓存下来,在缓存的有效时间内,以后访问这个方法都直接返回缓存结果,不再执行方法中的代码段。</p> <p>这个注解可以用 condition 属性来设置条件,如果不满足条件,就不使用缓存能力,直接执行方法。</p> <p>可以使用 key 属性来指定key的生成规则。</p> <h3><strong>@CachePut</strong></h3> <p>与 @Cacheable 不同, @CachePut 不仅会缓存方法的结果,还会执行方法的代码段。</p> <p>它支持的属性和用法都与 @Cacheable 一致。</p> <h3><strong>@CacheEvict</strong></h3> <p>与 @Cacheable 功能相反, @CacheEvict 表明所修饰的方法是用来删除失效或无用的缓存数据。</p> <p>下面是 @Cacheable 、 @CacheEvict 和 @CachePut 基本使用方法的一个集中展示:</p> <pre> <code class="language-java">@Service public class UserService { // @Cacheable可以设置多个缓存,形式如:@Cacheable({"books", "isbns"}) @Cacheable({"users"}) public User findUser(User user) { return findUserInDB(user.getId()); } @Cacheable(value = "users", condition = "#user.getId() <= 2") public User findUserInLimit(User user) { return findUserInDB(user.getId()); } @CachePut(value = "users", key = "#user.getId()") public void updateUser(User user) { updateUserInDB(user); } @CacheEvict(value = "users") public void removeUser(User user) { removeUserInDB(user.getId()); } @CacheEvict(value = "users", allEntries = true) public void clear() { removeAllInDB(); } }</code></pre> <h3><strong>@Caching</strong></h3> <p>如果需要使用同一个缓存注解( @Cacheable 、 @CacheEvict 或 @CachePut )多次修饰一个方法,就需要用到 @Caching 。</p> <pre> <code class="language-java">@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") }) public Book importBooks(String deposit, Date date)</code></pre> <h3><strong>@CacheConfig</strong></h3> <p>与前面的缓存注解不同,这是一个类级别的注解。</p> <p>如果类的所有操作都是缓存操作,你可以使用 @CacheConfig 来指定类,省去一些配置。</p> <pre> <code class="language-java">@CacheConfig("books") public class BookRepositoryImpl implements BookRepository { @Cacheable public Book findBook(ISBN isbn) {...} }</code></pre> <h2><strong>参考</strong></h2> <p><a href="/misc/goto?guid=4958868896503846653" rel="nofollow,noindex">Ehcache github</a></p> <p><a href="/misc/goto?guid=4959721579698516881" rel="nofollow,noindex">Ehcache官方文档</a></p> <p><a href="/misc/goto?guid=4959628602510677209" rel="nofollow,noindex">Ehcache详细解读</a></p> <p><a href="/misc/goto?guid=4959721579819600712" rel="nofollow,noindex">注释驱动的 Spring cache 缓存介绍</a></p> <p><a href="/misc/goto?guid=4959644883074511811" rel="nofollow,noindex">Spring官方文档4.3.3.RELEASE 第36章缓存抽象</a></p> <p> </p> <p>来自:http://www.cnblogs.com/jingmoxukong/p/5975994.html</p> <p> </p>