Spring Boot 快速整合 Ehcache3

聞人_發表於2019-02-20

Spring Boot 快速整合 Ehcache3

前言

在網際網路服務端架構中,快取的作用是尤為重要的,無論是基於伺服器的記憶體快取如 Redis,還是 基於 JVM 的記憶體快取如 Ehcache ,在高併發場景中承載著巨大的流量,本文主要針對 JVM 記憶體框架 Ehcache 3 進行簡單地練習,基於Spring Boot 整合 Ehcache 3 搭建一個簡單的專案,來實現程式的記憶體快取功能支援。

正文

Ehcache 3

Ehcache 是一個開源,具有高效能的 Java 快取庫,由於使用簡單,擴充套件性強,是使用最廣泛的 Java 快取框架,同時具備了記憶體快取和磁碟快取的能力,最新的版本是 Ehcache 3.6。

整合步驟

  1. 首先建立一個基本的 Spring Boot 程式取名為 springboot-ehcache,(版本為 2.1.3,以 maven 作為構建工具,不選擇任何依賴。(本專案採用 IDEA 2018.5)

  2. 在專案的 pom.xml 裡新增 Ehcache 3 依賴,選擇合適的版本,這裡採用了3.0.0。

pom.xml

<dependency>
  <groupId>org.ehcache</groupId>
  <artifactId>ehcache</artifactId>
  <version>3.0.0</version>    
 </dependency>
複製程式碼
  1. 在專案的 pom.xml 裡新增 JSR-107 API 依賴

    關於 JSR-107 API:Java 快取規範的文件 API,類似 JDBC 規範。

    pom.xml

    <dependency>
        <groupId>javax.cache</groupId>
        <artifactId>cache-api</artifactId>
    </dependency>
    複製程式碼
  2. 新增 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,方便測試快取的效果。

  3. 在程式配置檔案 application.properties 中指定 ehcache.xml 的路徑,一般放置在當前 classpath 下;這樣就讓 Spring 快取啟用 Ehcache。

    application.properties

    spring.cache.jcache.config=classpath:ehcache.xml
    複製程式碼
  4. 在專案裡啟用快取,有註解和 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>
    複製程式碼
  1. 在需要使用快取的方法上使用註解 @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 預設視為該方法名稱。
  2. 在 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。

  3. 實現 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));
        }
    }
    複製程式碼
  4. 建立一個控制器 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,可以從控制檯看到如下結果:

    Spring Boot 快速整合 Ehcache3

    可以看出第二次訪問時,直接使用的先前快取的資料。由於快取過期策略設定為 10秒,過了10秒再訪問一次檢視日誌,可以根據事件日誌器看出快取失效後重新獲取的資料,再新增到快取中去。

    Spring Boot 快速整合 Ehcache3

到這裡,我們的 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 未指定快取名稱

Spring Boot 快速整合 Ehcache3

解決辦法:@CacheResult 的 cacheName 必須指定配置建立的快取 ,否則 cacheName 預設為該方法完全名稱。

  • 問題三:沒有正確定義 事件日誌器,導致 cacheManager 建立快取出錯

Spring Boot 快速整合 Ehcache3

解決辦法:在 ehcache.xml 定義的 Listener 類實現 介面 CacheEventListener

結語

希望文章對你有所幫助,如果覺得還行,不妨點個贊吧。^_^

相關文章