一:目錄
- EhCache 簡介
- Hello World 示例
- Spring 整合
二: 簡介
2.1、基本介紹
EhCache 是一個純Java的進程內緩存框架,具有快速、精干等特點,是Hibernate中默認CacheProvider。Ehcache是一種廣泛使用的開源Java分布式緩存。主要面向通用緩存,Java EE和輕量級容器。它具有內存和磁盤存儲,緩存加載器,緩存擴展,緩存異常處理程序,一個gzip緩存servlet過濾器,支持REST和SOAP api等特點。
Spring 提供了對緩存功能的抽象:即允許綁定不同的緩存解決方案(如Ehcache),但本身不直接提供緩存功能的實現。它支持注解方式使用緩存,非常方便。
2.2、 主要的特性
- 快速
- 簡單
- 多種緩存策略
- 緩存數據有兩級:內存和磁盤,因此無需擔心容量問題
- 緩存數據會在虛擬機重啟的過程中寫入磁盤
- 可以通過RMI、可插入API等方式進行分布式緩存
- 具有緩存和緩存管理器的偵聽接口
- 支持多緩存管理器實例,以及一個實例的多個緩存區域
- 提供Hibernate的緩存實現
2.3、 集成
可以單獨使用,一般在第三方庫中被用到的比較多(如mybatis、shiro等)ehcache 對分布式支持不夠好,多個節點不能同步,通常和redis一塊使用
2.4、 ehcache 和 redis 比較
ehcache直接在jvm虛擬機中緩存,速度快,效率高;但是緩存共享麻煩,集群分布式應用不方便。
redis是通過socket訪問到緩存服務,效率比Ehcache低,比數據庫要快很多,處理集群和分布式緩存方便,有成熟的方案。如果是單個應用或者對緩存訪問要求很高的應用,用ehcache。如果是大型系統,存在緩存共享、分布式部署、緩存內容很大的,建議用redis。
ehcache也有緩存共享方案,不過是通過RMI或者Jgroup多播方式進行廣播緩存通知更新,緩存共享復雜,維護不方便;簡單的共享可以,但是涉及到緩存恢復,大數據緩存,則不合適。
三:事例
3.1、在pom.xml中引入依賴
<dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.10.2</version> </dependency>
3.2、在src/main/resources/創建一個配置文件 ehcache.xml
默認情況下Ehcache會自動加載classpath根目錄下名為ehcache.xml文件,也可以將該文件放到其他地方在使用時指定文件的位置
<?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> <!-- helloworld緩存 --> <cache name="HelloWorldCache" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="5" timeToLiveSeconds="5" overflowToDisk="false" memoryStoreEvictionPolicy="LRU"/> </ehcache>
3.3、測試類
import entity.Dog; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Element; public class CacheTest { public static void main(String[] args) { // 1. 創建緩存管理器 CacheManager cacheManager = CacheManager.create("./src/main/resources/ehcache.xml"); // 2. 獲取緩存對象 Cache cache = cacheManager.getCache("HelloWorldCache"); // 3. 創建元素 Element element = new Element("key1", "value1"); // 4. 將元素添加到緩存 cache.put(element); // 5. 獲取緩存 Element value = cache.get("key1"); System.out.println("value: " + value); System.out.println(value.getObjectValue()); // 6. 刪除元素 cache.remove("key1"); Dog dog = new Dog("xiaohei", "black", 2); Element element2 = new Element("dog", dog); cache.put(element2); Element value2 = cache.get("dog"); System.out.println("value2: " + value2); Dog dog2 = (Dog) value2.getObjectValue(); System.out.println(dog2); System.out.println(cache.getSize()); // 7. 刷新緩存 cache.flush(); // 8. 關閉緩存管理器 cacheManager.shutdown(); } }
public class Dog { private String name; private String color; private int age; public Dog() { } public Dog(String name, String color, int age) { super(); this.name = name; this.color = color; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Dog [name=" + name + ", color=" + color + ", age=" + age + "]"; } }
3.4、緩存配置
一:xml配置方式:
diskStore : ehcache支持內存和磁盤兩種存儲
path :指定磁盤存儲的位置
defaultCache : 默認的緩存
maxEntriesLocalHeap=“10000”
eternal=“false”
timeToIdleSeconds=“120”
timeToLiveSeconds=“120”
maxEntriesLocalDisk=“10000000”
diskExpiryThreadIntervalSeconds=“120”
memoryStoreEvictionPolicy=“LRU”
cache :自定的緩存,當自定的配置不滿足實際情況時可以通過自定義(可以包含多個cache節點)
name : 緩存的名稱,可以通過指定名稱獲取指定的某個Cache對象
maxElementsInMemory :內存中允許存儲的最大的元素個數,0代表無限個
clearOnFlush:內存數量最大時是否清除。
eternal :設置緩存中對象是否為永久的,如果是,超時設置將被忽略,對象從不過期。根據存儲數據的不同,例如一些靜態不變的數據如省市區等可以設置為永不過時
timeToIdleSeconds : 設置對象在失效前的允許閑置時間(單位:秒)。僅當eternal=false對象不是永久有效時使用,可選屬性,默認值是0,也就是可閑置時間無窮大。
timeToLiveSeconds :緩存數據的生存時間(TTL),也就是一個元素從構建到消亡的最大時間間隔值,這只能在元素不是永久駐留時有效,如果該值是0就意味着元素可以停頓無窮長的時間。
overflowToDisk :內存不足時,是否啟用磁盤緩存。
maxEntriesLocalDisk:當內存中對象數量達到maxElementsInMemory時,Ehcache將會對象寫到磁盤中。
maxElementsOnDisk:硬盤最大緩存個數。
diskSpoolBufferSizeMB:這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每個Cache都應該有自己的一個緩沖區。
diskPersistent:是否在VM重啟時存儲硬盤的緩存數據。默認值是false。
diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認是120秒。
二:編程方式配置
Cache cache = manager.getCache("mycache"); CacheConfiguration config = cache.getCacheConfiguration(); config.setTimeToIdleSeconds(60); config.setTimeToLiveSeconds(120); config.setmaxEntriesLocalHeap(10000); config.setmaxEntriesLocalDisk(1000000);
3.5、Ehcache API
- CacheManager:Cache的容器對象,並管理着(添加或刪除)Cache的生命周期。
// 可以自己創建一個Cache對象添加到CacheManager中 public void addCache(Cache cache); public synchronized void removeCache(String cacheName);
Cache: 一個Cache可以包含多個Element,並被CacheManager管理。它實現了對緩存的邏輯行為
Element:需要緩存的元素,它維護着一個鍵值對, 元素也可以設置有效期,0代表無限制
獲取CacheManager的方式:
可以通過create()或者newInstance()方法或重載方法來創建獲取CacheManager的方式:
public static CacheManager create(); public static CacheManager create(String configurationFileName); public static CacheManager create(InputStream inputStream); public static CacheManager create(URL configurationFileURL); public static CacheManager newInstance();
Ehcache的CacheManager構造函數或工廠方法被調用時,會默認加載classpath下名為ehcache.xml的配置文件。
如果加載失敗,會加載Ehcache jar包中的ehcache-failsafe.xml文件,這個文件中含有簡單的默認配置。
// CacheManager.create() == CacheManager.create("./src/main/resources/ehcache.xml") // 使用Ehcache默認配置新建一個CacheManager實例 CacheManager cacheManager = CacheManager.create(); cacheManager = CacheManager.newInstance(); cacheManager = CacheManager.newInstance("./src/main/resources/ehcache.xml"); InputStream inputStream = new FileInputStream(new File("./src/main/resources/ehcache.xml")); cacheManager = CacheManager.newInstance(inputStream); String[] cacheNames = cacheManager.getCacheNames(); // [HelloWorldCache]
四:Spring整合
項目結構:
4.1、pom.xml 引入spring和ehcache
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.gdut.yh</groupId> <artifactId>EhcacheSpringTest</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <junit.version>4.10</junit.version> <spring.version>4.2.3.RELEASE</spring.version> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <!-- springframework --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.10.3</version> </dependency> </dependencies> </project>
4.2、在src/main/resources添加ehcache.xml
<?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> <!-- helloworld緩存 --> <cache name="HelloWorldCache" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="5" timeToLiveSeconds="5" overflowToDisk="false" memoryStoreEvictionPolicy="LRU"/> <cache name="UserCache" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800" overflowToDisk="false" memoryStoreEvictionPolicy="LRU"/> </ehcache>
4.3、在src/main/resources/conf/spring中配置spring-base.xml和spring-ehcache.xml
spring-base.xml
<?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:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <context:component-scan base-package="com.gdut.*"/> </beans>
spring-ehcache.xml
<?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> <!-- 啟用緩存注解開關 --> <cache:annotation-driven cache-manager="cacheManager"/> <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"> <property name="cacheManager" ref="ehcache"/> </bean> <bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"> <property name="configLocation" value="classpath:ehcache.xml"/> </bean> </beans>
4.4、在src/main/java/com.mengdee.manager.service/下 創建EhcacheService和EhcacheServiceImpl
EhcacheService.java
public interface EhcacheService { // 測試失效情況,有效期為5秒 public String getTimestamp(String param); public String getDataFromDB(String key); public void removeDataAtDB(String key); public String refreshData(String key); public User findById(String userId); public boolean isReserved(String userId); public void removeUser(String userId); public void removeAllUser(); }
EhcacheServiceImpl.java
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @Service public class EhcacheServiceImpl implements EhcacheService{ // value的值和ehcache.xml中的配置保持一致 @Cacheable(value="HelloWorldCache", key="#param") public String getTimestamp(String param) { Long timestamp = System.currentTimeMillis(); return timestamp.toString(); } @Cacheable(value="HelloWorldCache", key="#key") public String getDataFromDB(String key) { System.out.println("從數據庫中獲取數據..."); return key + ":" + String.valueOf(Math.round(Math.random()*1000000)); } @CacheEvict(value="HelloWorldCache", key="#key") public void removeDataAtDB(String key) { System.out.println("從數據庫中刪除數據"); } @CachePut(value="HelloWorldCache", key="#key") public String refreshData(String key) { System.out.println("模擬從數據庫中加載數據"); return key + "::" + String.valueOf(Math.round(Math.random()*1000000)); } // ------------------------------------------------------------------------ @Cacheable(value="UserCache", key="'user:' + #userId") public User findById(String userId) { System.out.println("模擬從數據庫中查詢數據"); return new User(1, "mengdee"); } @Cacheable(value="UserCache", condition="#userId.length()<12") public boolean isReserved(String userId) { System.out.println("UserCache:"+userId); return false; } //清除掉UserCache中某個指定key的緩存 @CacheEvict(value="UserCache",key="'user:' + #userId") public void removeUser(String userId) { System.out.println("UserCache remove:"+ userId); } //allEntries:true表示清除value中的全部緩存,默認為false //清除掉UserCache中全部的緩存 @CacheEvict(value="UserCache", allEntries=true) public void removeAllUser() { System.out.println("UserCache delete all"); } }
User .java
public class User { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public User() { } public User(int id, String name) { super(); this.id = id; this.name = name; } @Override public String toString() { return "User [id=" + id + ", name=" + name + "]"; } }
#注解基本使用方法
Spring對緩存的支持類似於對事務的支持。
首先使用注解標記方法,相當於定義了切點,然后使用Aop技術在這個方法的調用前、調用后獲取方法的入參和返回值,進而實現了緩存的邏輯。
@Cacheable
表明所修飾的方法是可以緩存的:當第一次調用這個方法時,它的結果會被緩存下來,在緩存的有效時間內,以后訪問這個方法都直接返回緩存結果,不再執行方法中的代碼段。
這個注解可以用condition屬性來設置條件,如果不滿足條件,就不使用緩存能力,直接執行方法。
可以使用key屬性來指定key的生成規則。
@Cacheable 支持如下幾個參數:
value:緩存位置名稱,不能為空,如果使用EHCache,就是ehcache.xml中聲明的cache的name, 指明將值緩存到哪個Cache中
key:緩存的key,默認為空,既表示使用方法的參數類型及參數值作為key,支持SpEL,如果要引用參數值使用井號加參數名,如:#userId,一般來說,我們的更新操作只需要刷新緩存中某一個值,所以定義緩存的key值的方式就很重要,最好是能夠唯一,因為這樣可以准確的清除掉特定的緩存,而不會影響到其它緩存值 ,本例子中使用實體加冒號再加ID組合成鍵的名稱,如"user:1"、"order:223123"等
condition:觸發條件,只有滿足條件的情況才會加入緩存,默認為空,既表示全部都加入緩存,支持SpEL
// 將緩存保存到名稱為UserCache中,鍵為"user:"字符串加上userId值,如 'user:1' @Cacheable(value="UserCache", key="'user:' + #userId") public User findById(String userId) { return (User) new User("1", "mengdee"); } // 將緩存保存進UserCache中,並當參數userId的長度小於12時才保存進緩存,默認使用參數值及類型作為緩存的key // 保存緩存需要指定key,value, value的數據類型,不指定key默認和參數名一樣如:"1" @Cacheable(value="UserCache", condition="#userId.length() < 12") public boolean isReserved(String userId) { System.out.println("UserCache:"+userId); return false; }
@CachePut
與@Cacheable不同,@CachePut不僅會緩存方法的結果,還會執行方法的代碼段。它支持的屬性和用法都與@Cacheable一致。
@CacheEvict
與@Cacheable功能相反,@CacheEvict表明所修飾的方法是用來刪除失效或無用的緩存數據。
@CacheEvict 支持如下幾個參數:
value:緩存位置名稱,不能為空,同上
key:緩存的key,默認為空,同上
condition:觸發條件,只有滿足條件的情況才會清除緩存,默認為空,支持SpEL
allEntries:true表示清除value中的全部緩存,默認為false
//清除掉UserCache中某個指定key的緩存 @CacheEvict(value="UserCache",key="'user:' + #userId") public void removeUser(User user) { System.out.println("UserCache"+user.getUserId()); } //清除掉UserCache中全部的緩存 @CacheEvict(value="UserCache", allEntries=true) public final void setReservedUsers(String[] reservedUsers) { System.out.println("UserCache deleteall"); }
5、測試
SpringTestCase.java
import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @ContextConfiguration(locations = {"classpath:spring-base.xml","classpath:spring-ehcache.xml"}) @RunWith(SpringJUnit4ClassRunner.class) public class SpringTestCase extends AbstractJUnit4SpringContextTests{ }
EhcacheServiceTest.java
import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import com.gdut.ehcache.EhcacheService; public class EhcacheServiceTest extends SpringTestCase{ @Autowired //@Autowired 是通過 byType 的方式去注入的, 使用該注解,要求接口只能有一個實現類。 private EhcacheService ehcacheService; // 有效時間是5秒,第一次和第二次獲取的值是一樣的,因第三次是5秒之后所以會獲取新的值 @Test public void testTimestamp() throws InterruptedException{ System.out.println("第一次調用:" + ehcacheService.getTimestamp("param")); Thread.sleep(2000); System.out.println("2秒之后調用:" + ehcacheService.getTimestamp("param")); Thread.sleep(4000); System.out.println("再過4秒之后調用:" + ehcacheService.getTimestamp("param")); } @Test public void testCache(){ String key = "zhangsan"; String value = ehcacheService.getDataFromDB(key); // 從數據庫中獲取數據... ehcacheService.getDataFromDB(key); // 從緩存中獲取數據,所以不執行該方法體 ehcacheService.removeDataAtDB(key); // 從數據庫中刪除數據 ehcacheService.getDataFromDB(key); // 從數據庫中獲取數據...(緩存數據刪除了,所以要重新獲取,執行方法體) } @Test public void testPut(){ String key = "mengdee"; ehcacheService.refreshData(key); // 模擬從數據庫中加載數據 String data = ehcacheService.getDataFromDB(key); System.out.println("data:" + data); // data:mengdee::103385 ehcacheService.refreshData(key); // 模擬從數據庫中加載數據 String data2 = ehcacheService.getDataFromDB(key); System.out.println("data2:" + data2); // data2:mengdee::180538 } @Test public void testFindById(){ ehcacheService.findById("2"); // 模擬從數據庫中查詢數據 ehcacheService.findById("2"); } @Test public void testIsReserved(){ ehcacheService.isReserved("123"); ehcacheService.isReserved("123"); } @Test public void testRemoveUser(){ // 線添加到緩存 ehcacheService.findById("1"); // 再刪除 ehcacheService.removeUser("1"); // 如果不存在會執行方法體 ehcacheService.findById("1"); } @Test public void testRemoveAllUser(){ ehcacheService.findById("1"); ehcacheService.findById("2"); ehcacheService.removeAllUser(); ehcacheService.findById("1"); ehcacheService.findById("2"); // 模擬從數據庫中查詢數據 // 模擬從數據庫中查詢數據 // UserCache delete all // 模擬從數據庫中查詢數據 // 模擬從數據庫中查詢數據 } }