Spring整合Ehcache管理緩存
前言
Ehcache 是一個成熟的緩存框架,你可以直接使用它來管理你的緩存。
Spring 提供了對緩存功能的抽象:即允許綁定不同的緩存解決方案(如Ehcache),但本身不直接提供緩存功能的實現。它支持注解方式使用緩存,非常方便。
本文先通過Ehcache獨立應用的范例來介紹它的基本使用方法,然后再介紹與Spring整合的方法。
概述
Ehcache是什么?
EhCache 是一個純Java的進程內緩存框架,具有快速、精干等特點。它是Hibernate中的默認緩存框架。
Ehcache已經發布了3.1版本。但是本文的講解基于2.10.2版本。
為什么不使用最新版呢?因為Spring4還不能直接整合Ehcache 3.x。雖然可以通過JCache間接整合,Ehcache也支持JCache,但是個人覺得不是很方便。
安裝
Ehcache
如果你的項目使用maven管理,添加以下依賴到你的pom.xml中。
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.2</version>
<type>pom</type>
</dependency>
如果你的項目不使用maven管理,請在 Ehcache官網下載地址 下載jar包。
Spring
如果你的項目使用maven管理,添加以下依賴到你的pom.xml中。
spring-context-support
這個jar包中含有Spring對于緩存功能的抽象封裝接口。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
Ehcache的使用
HelloWorld范例
接觸一種技術最快最直接的途徑總是一個Hello World例子,畢竟動手實踐印象更深刻,不是嗎?
(1) 在classpath下添加ehcache.xml
添加一個名為helloworld的緩存。
<?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>
(2) EhcacheDemo.java
Ehcache會自動加載classpath根目錄下名為ehcache.xml文件。
EhcacheDemo的工作步驟如下:
在EhcacheDemo中,我們引用ehcache.xml聲明的名為helloworld的緩存來創建Cache
對象;
然后我們用一個鍵值對來實例化Element
對象;
將Element
對象添加到Cache
;
然后用Cache
的get方法獲取Element
對象。
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());
}
}
輸出
Hello, World!
Ehcache基本操作
Element
、Cache
、CacheManager
是Ehcache最重要的API。
Element:緩存的元素,它維護著一個鍵值對。
Cache:它是Ehcache的核心類,它有多個
Element
,并被CacheManager
管理。它實現了對緩存的邏輯行為。-
CacheManager:
Cache
的容器對象,并管理著Cache
的生命周期。創建CacheManager
下面的代碼列舉了創建
CacheManager
的五種方式。
使用靜態方法create()
會以默認配置來創建單例的CacheManager
實例。
newInstance()
方法是一個工廠方法,以默認配置創建一個新的CacheManager
實例。
此外,newInstance()
還有幾個重載函數,分別可以通過傳入String
、URL
、InputStream
參數來加載配置文件,然后創建CacheManager
實例。
// 使用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();
}
添加緩存
需要強調一點,Cache對象在用addCache方法添加到CacheManager之前,是無效的。
使用CacheManager的addCache方法可以根據緩存名將ehcache.xml中聲明的cache添加到容器中;它也可以直接將Cache對象添加到緩存容器中。
Cache
有多個構造函數,提供了不同方式去加載緩存的配置參數。
有時候,你可能需要使用API來動態的添加緩存,下面的例子就提供了這樣的范例。
// 除了可以使用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);
刪除緩存
刪除緩存比較簡單,你只需要將指定的緩存名傳入removeCache
方法即可。
CacheManager singletonManager = CacheManager.create();
singletonManager.removeCache("sampleCache1");
實現基本緩存操作
Cache最重要的兩個方法就是put和get,分別用來添加Element和獲取Element。
Cache還提供了一系列的get、set方法來設置或獲取緩存參數,這里不一一列舉,更多API操作可參考官方API開發手冊。
/**
* 測試:使用默認配置或使用指定配置來創建CacheManager
*
* @author Zhang Peng
*/
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();
}
}
緩存配置
Ehcache支持通過xml文件和API兩種方式進行配置。
xml方式
Ehcache的CacheManager
構造函數或工廠方法被調用時,會默認加載classpath下名為ehcache.xml的配置文件。如果加載失敗,會加載Ehcache jar包中的ehcache-failsafe.xml文件,這個文件中含有簡單的默認配置。
ehcache.xml配置參數說明:
- name:緩存名稱。
- maxElementsInMemory:緩存最大個數。
- eternal:緩存中對象是否為永久的,如果是,超時設置將被忽略,對象從不過期。
- timeToIdleSeconds:置對象在失效前的允許閑置時間(單位:秒)。僅當eternal=false對象不是永久有效時使用,可選屬性,默認值是0,也就是可閑置時間無窮大。
- timeToLiveSeconds:緩存數據的生存時間(TTL),也就是一個元素從構建到消亡的最大時間間隔值,這只能在元素不是永久駐留時有效,如果該值是0就意味著元素可以停頓無窮長的時間。
- maxEntriesLocalDisk:當內存中對象數量達到maxElementsInMemory時,Ehcache將會對象寫到磁盤中。
- overflowToDisk:內存不足時,是否啟用磁盤緩存。
- diskSpoolBufferSizeMB:這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每個Cache都應該有自己的一個緩沖區。
- maxElementsOnDisk:硬盤最大緩存個數。
- diskPersistent:是否在VM重啟時存儲硬盤的緩存數據。默認值是false。
- diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認是120秒。
- memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理內存。默認策略是LRU(最近最少使用)。你可以設置為FIFO(先進先出)或是LFU(較少使用)。
- clearOnFlush:內存數量最大時是否清除。
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>
<cache name="userCache"
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="3"
timeToLiveSeconds="3"
maxEntriesLocalDisk="10000000"
overflowToDisk="false"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
API方式
xml配置的參數也可以直接通過編程方式來動態的進行配置(dynamicConfig沒有設為false)。
Cache cache = manager.getCache("sampleCache");
CacheConfiguration config = cache.getCacheConfiguration();
config.setTimeToIdleSeconds(60);
config.setTimeToLiveSeconds(120);
config.setmaxEntriesLocalHeap(10000);
config.setmaxEntriesLocalDisk(1000000);
也可以通過disableDynamicFeatures()
方式關閉動態配置開關。配置以后你將無法再以編程方式配置參數。
Cache cache = manager.getCache("sampleCache");
cache.disableDynamicFeatures();
Spring整合Ehcache
Spring3.1開始添加了對緩存的支持。和事務功能的支持方式類似,緩存抽象允許底層使用不同的緩存解決方案來進行整合。
Spring4.1開始支持JSR-107注解。
注:我本人使用的Spring版本為4.1.4.RELEASE,目前Spring版本僅支持Ehcache2.5以上版本,但不支持Ehcache3。
綁定Ehcache
org.springframework.cache.ehcache.EhCacheManagerFactoryBean
這個類的作用是加載Ehcache配置文件。
org.springframework.cache.ehcache.EhCacheCacheManager
這個類的作用是支持net.sf.ehcache.CacheManager。
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>
<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>
使用Spring的緩存注解
開啟注解
Spring為緩存功能提供了注解功能,但是你必須啟動注解。
你有兩個選擇:
(1) 在xml中聲明
像上一節spring-ehcache.xml中的做法一樣,使用<cache:annotation-driven/>
<cache:annotation-driven cache-manager="cacheManager"/>
(2) 使用標記注解
你也可以通過對一個類進行注解修飾的方式在這個類中使用緩存注解。
范例如下:
@Configuration
@EnableCaching
public class AppConfig {
}
注解基本使用方法
Spring對緩存的支持類似于對事務的支持。
首先使用注解標記方法,相當于定義了切點,然后使用Aop技術在這個方法的調用前、調用后獲取方法的入參和返回值,進而實現了緩存的邏輯。
下面三個注解都是方法級別:
@Cacheable
表明所修飾的方法是可以緩存的:當第一次調用這個方法時,它的結果會被緩存下來,在緩存的有效時間內,以后訪問這個方法都直接返回緩存結果,不再執行方法中的代碼段。
這個注解可以用condition
屬性來設置條件,如果不滿足條件,就不使用緩存能力,直接執行方法。
可以使用key
屬性來指定key的生成規則。
@CachePut
與@Cacheable
不同,@CachePut
不僅會緩存方法的結果,還會執行方法的代碼段。
它支持的屬性和用法都與@Cacheable
一致。
@CacheEvict
與@Cacheable
功能相反,@CacheEvict
表明所修飾的方法是用來刪除失效或無用的緩存數據。
下面是@Cacheable
、@CacheEvict
和@CachePut
基本使用方法的一個集中展示:
@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();
}
}
@Caching
如果需要使用同一個緩存注解(@Cacheable
、@CacheEvict
或@CachePut
)多次修飾一個方法,就需要用到@Caching
。
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)
@CacheConfig
與前面的緩存注解不同,這是一個類級別的注解。
如果類的所有操作都是緩存操作,你可以使用@CacheConfig
來指定類,省去一些配置。
@CacheConfig("books")
public class BookRepositoryImpl implements BookRepository {
@Cacheable
public Book findBook(ISBN isbn) {...}
}