Spring 整合 Ehcache 管理快取詳解

靜默虛空發表於2016-10-23

前言

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基本操作

ElementCacheCacheManager是Ehcache最重要的API。

  • Element:快取的元素,它維護著一個鍵值對。
  • Cache:它是Ehcache的核心類,它有多個Element,並被CacheManager管理。它實現了對快取的邏輯行為。
  • CacheManager:Cache的容器物件,並管理著Cache的生命週期。

建立CacheManager

下面的程式碼列舉了建立CacheManager的五種方式。
使用靜態方法create()會以預設配置來建立單例的CacheManager例項。
newInstance()方法是一個工廠方法,以預設配置建立一個新的CacheManager例項。
此外,newInstance()還有幾個過載函式,分別可以通過傳入StringURLInputStream引數來載入配置檔案,然後建立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 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();
    }
}

快取配置

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) {...}
}

參考

如果想參考我的完整程式碼示例,請點選這裡訪問我的github。

下面是我在寫作時參考的資料或文章。

相關文章