Spring Boot 快速整合 Ehcache3
前言
在網際網路服務端架構中,快取的作用是尤為重要的,無論是基於伺服器的記憶體快取如 Redis,還是 基於 JVM 的記憶體快取如 Ehcache ,在高併發場景中承載著巨大的流量,本文主要針對 JVM 記憶體框架 Ehcache 3 進行簡單地練習,基於Spring Boot 整合 Ehcache 3 搭建一個簡單的專案,來實現程式的記憶體快取功能支援。
正文
Ehcache 3
Ehcache 是一個開源,具有高效能的 Java 快取庫,由於使用簡單,擴充套件性強,是使用最廣泛的 Java 快取框架,同時具備了記憶體快取和磁碟快取的能力,最新的版本是 Ehcache 3.6。
整合步驟
-
首先建立一個基本的 Spring Boot 程式取名為
springboot-ehcache
,(版本為 2.1.3,以 maven 作為構建工具,不選擇任何依賴。(本專案採用 IDEA 2018.5) -
在專案的 pom.xml 裡新增 Ehcache 3 依賴,選擇合適的版本,這裡採用了3.0.0。
pom.xml
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.0.0</version>
</dependency>
複製程式碼
-
在專案的 pom.xml 裡新增 JSR-107 API 依賴
關於 JSR-107 API:Java 快取規範的文件 API,類似 JDBC 規範。
pom.xml
<dependency> <groupId>javax.cache</groupId> <artifactId>cache-api</artifactId> </dependency> 複製程式碼
-
新增 Spring Boot 依賴
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> 複製程式碼
前兩個依賴是 Spring Boot 程式 建立時預設有的,這裡的
spring-boot-starter-cache
就是使用 Spring 框架的快取功能,而加入了spring-boot-starter-web
主要為了引入 Spring MVC,方便測試快取的效果。 -
在程式配置檔案 application.properties 中指定 ehcache.xml 的路徑,一般放置在當前 classpath 下;這樣就讓 Spring 快取啟用 Ehcache。
application.properties
spring.cache.jcache.config=classpath:ehcache.xml 複製程式碼
-
在專案裡啟用快取,有註解和 XML 配置兩種方式
-
使用 @EnableCaching 註解
// com.one.springbootehcache2.SpringbootEhcacheApplication.java @EnableCaching @SpringBootApplication public class SpringbootEhcacheApplication { public static void main(String[] args) { SpringApplication.run(SpringbootEhcacheApplication.class, args); } } 複製程式碼
-
或者在 Spring 的 XML 檔案中新增
<cache:annotation-driven />
<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.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"> <cache:annotation-driven /> </beans> 複製程式碼
-
在需要使用快取的方法上使用註解 @CacheResult 進行宣告,這樣一旦呼叫這個方法,返回的結果就會被快取,除非快取被清除掉,下次就不會執行方法的邏輯了。
PersonService.java
// com.one.springbootehcache.service.PersonService.java @Service public class PersonService { @CacheResult(cacheName="people") Person getPerson(int id) { System.out.println("未從快取讀取 " + id); switch (id) { case 1: return new Person(id, "Steve", "jobs"); case 2: return new Person(id, "bill", "gates"); default: return new Person(id, "unknown", "unknown"); } } } 複製程式碼
Person.java
// com.one.springbootehcache.domain.Person.java public class Person implements Serializable { private int id; private String firstName; private String lastName; public Person(int id, String firstName, String lastName) { this.id =id; this.firstName = firstName; this.lastName = lastName; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } } 複製程式碼
- @CacheResult 必須指定 cacheName,否則 cacheName 預設視為該方法名稱。
-
在 ehcache.xml 配置基本快取引數
ehcache.xml
<config xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns='http://www.ehcache.org/v3' xmlns:jsr107='http://www.ehcache.org/v3/jsr107'> <service> <jsr107:defaults> <jsr107:cache name="people" template="heap-cache"/> </jsr107:defaults> </service> <cache-template name="heap-cache"> <listeners> <listener> <class>com.one.springbootehcache.config.EventLogger</class> <event-firing-mode>ASYNCHRONOUS</event-firing-mode> <event-ordering-mode>UNORDERED</event-ordering-mode> <events-to-fire-on>CREATED</events-to-fire-on> <events-to-fire-on>UPDATED</events-to-fire-on> <events-to-fire-on>EXPIRED</events-to-fire-on> <events-to-fire-on>REMOVED</events-to-fire-on> </listener> </listeners> <resources> <heap unit="entries">2000</heap> <offheap unit="MB">100</offheap> </resources> </cache-template> </config> 複製程式碼
-
宣告一個名為 people 的快取,指定 heap-cache 為模板
-
在快取模板裡配置了日誌輸入器 EventLogger,用來監聽快取資料變更的事件,例如資料建立,更新,失效等進行事件日誌輸出。
//com.one.springbootehcache.config.EventLogger.java public class EventLogger implements CacheEventListener<Object, Object> { private static final Logger LOGGER = LoggerFactory.getLogger(EventLogger.class); @Override public void onEvent(CacheEvent<Object, Object> event) { LOGGER.info("Event: " + event.getType() + " Key: " + event.getKey() + " old value: " + event.getOldValue() + " new value: " + event.getNewValue()); } } 複製程式碼
-
對 CREATED,UPDATED,EXPIRED,REMOVED 這四個事件進行監聽。
-
最後的 resources 元素配置了快取能容納的最大物件個數為2000,堆外記憶體容量為100M。
-
-
實現
JCacheManagerCustomizer.customize(CacheManager cacheManager)
方法在 CacheManager 使用之前,建立我們配置檔案定義的快取,並宣告瞭快取策略為10秒。// com.one.springbootehcache.config.CachingSetup.java @Component public class CachingSetup implements JCacheManagerCustomizer { @Override public void customize(CacheManager cacheManager) { cacheManager.createCache("people", new MutableConfiguration<>() .setExpiryPolicyFactory(TouchedExpiryPolicy.factoryOf(new Duration(SECONDS, 10))) .setStoreByValue(false) .setStatisticsEnabled(true)); } } 複製程式碼
-
建立一個控制器 PersonController,進行快取的測試。
// com.one.springbootehcache.domain.Person.java @RequestMapping("/person") @RestController public class PersonController { private static final Logger LOGGER = LoggerFactory.getLogger(PersonController.class); @Autowired private PersonService personService; @RequestMapping("/get") public Person getPerson(int id) { Person person = personService.getPerson(id); LOGGER.info("讀取到資料 " + person.getFirstName() + "," + person.getLastName()); return person; } } 複製程式碼
啟動程式,快速兩次訪問
http://localhost:8080/person/get?id=1
,可以從控制檯看到如下結果:可以看出第二次訪問時,直接使用的先前快取的資料。由於快取過期策略設定為 10秒,過了10秒再訪問一次檢視日誌,可以根據事件日誌器看出快取失效後重新獲取的資料,再新增到快取中去。
到這裡,我們的 Ehcache 3 與 Spring Boot 整合整合就算完成了,雖然專案比較簡單,但可以基於此參考更詳細的 Ehcache 配置來進行擴充套件。
問題列表
下面是我搭建專案過程中踩到的坑,這裡放出來,希望能對同樣遇到問題的同學有所參考。
- **問題一:實體類未實現 java.io.Serializable 介面 **
2019-02-17 15:50:07.606 ERROR 29671 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.data.redis.serializer.SerializationException: Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.one.springbootehcache.domain.Person]] with root cause
java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.one.springbootehcache.domain.Person]
複製程式碼
解決辦法:Ehcahe 需要快取的實體類必須實現 java.io.Serializable 介面
- 問題二:註解 @CacheResult 未指定快取名稱
解決辦法:@CacheResult 的 cacheName 必須指定配置建立的快取 ,否則 cacheName 預設為該方法完全名稱。
- 問題三:沒有正確定義 事件日誌器,導致 cacheManager 建立快取出錯
解決辦法:在 ehcache.xml 定義的 Listener 類實現 介面
CacheEventListener
。
結語
希望文章對你有所幫助,如果覺得還行,不妨點個贊吧。^_^