EhCache 是一個純Java的進程內緩存框架,具有快速、精干等特點,是Hibernate中默認CacheProvider。Ehcache是一種廣泛使用的開源Java分布式緩存。主要面向通用緩存,Java EE和輕量級容器。它具有內存和磁盤存儲,緩存加載器,緩存擴展,緩存異常處理程序,一個gzip緩存servlet過濾器,支持REST和SOAP API等特點。
主要的特性有:
-
快速
-
簡單
-
多種緩存策略
-
緩存數據有兩級:內存和磁盤,因此無需擔心容量問題
-
緩存數據會在虛擬機重啟的過程中寫入磁盤
-
可以通過RMI、可插入API等方式進行分布式緩存
-
具有緩存和緩存管理器的偵聽接口
-
支持多緩存管理器實例,以及一個實例的多個緩存區域
-
提供Hibernate的緩存實現
二、Spring緩存抽象
Spring從3.1開始定義了org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口來統一不同的緩存技術;
-
Cache接口為緩存的組件規范定義,包含緩存的各種操作集合;
-
Cache接口下spring提供了各種xxxCache的實現,比如EhCacheCache、RedisCache等等
-
每次調用需要緩存功能的方法時,Spring會檢查指定參數的指定目標方法是否已經被調用過;如果有緩存就直接從緩存中獲取結果,沒有就調用方法並緩存結果后返回給用戶。下次調用則直接從緩存中獲取。
1、緩存注解概念
| Cache | 緩存接口,定義緩存操作。實現有:EhCacheCache、RedisCache等等 |
|---|---|
| CacheManager | 緩存管理器,管理各種緩存組件 |
| @Cacheable | 主要針對方法配置,能夠根據方法的請求參數對其結果進行緩存 |
| @CacheEvict | 清空緩存 |
| @CachePut | 保證方法被調用,又希望結果被緩存 |
| @EnableCaching | 開啟基於注解的緩存 |
三、SpringBoot 添加 EhCache緩存
1、pom.xml 添加依賴
<!--ehcache 緩存--> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.10.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
2、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="E:\data"/> <!-- 默認緩存 --> <defaultCache maxEntriesLocalHeap="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" maxEntriesLocalDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <persistence strategy="localTempSwap"/> </defaultCache> <!-- cache 可以設置多個,例如localCache、UserCache和HelloWorldCache --> <cache name="localCache" eternal="true" maxElementsInMemory="100" maxElementsOnDisk="1000" overflowToDisk="true" diskPersistent="true" timeToIdleSeconds="0" timeToLiveSeconds="0" memoryStoreEvictionPolicy="LRU"/> <cache name="UserCache" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="10" timeToLiveSeconds="10" overflowToDisk="false" memoryStoreEvictionPolicy="LRU"/> <!-- hello world緩存 --> <cache name="HelloWorldCache" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="5" timeToLiveSeconds="5" overflowToDisk="false" memoryStoreEvictionPolicy="LRU"/> <!-- memoryStoreEvictionPolicy Ehcache將會根據指定的策略去清理內存。默認策略是LRU(最近最少使用)--> <!-- 緩存配置 name:緩存名稱。 maxElementsInMemory:緩存最大個數。 eternal:對象是否永久有效,一但設置了,timeout將不起作用。 timeToIdleSeconds:設置對象在失效前的允許閑置時間(單位:秒)。僅當eternal=false對象不是永久有效時使用,可選屬性,默認值是0,也就是可閑置時間無窮大。 timeToLiveSeconds:設置對象在失效前允許存活時間(單位:秒)。最大時間介於創建時間和失效時間之間。僅當eternal=false對象不是永久有效時使用,默認是0.,也就是對象存活時間無窮大。 overflowToDisk:當內存中對象數量達到maxElementsInMemory時,Ehcache將會對象寫到磁盤中。 diskSpoolBufferSizeMB:這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每個Cache都應該有自己的一個緩沖區。 maxElementsOnDisk:硬盤最大緩存個數。 diskPersistent:是否緩存虛擬機重啟期數據 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認是120秒。 memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理內存。默認策略是LRU(最近最少使用)。你可以設置為FIFO(先進先出)或是LFU(較少使用)。 clearOnFlush:內存數量最大時是否清除。 --> </ehcache>
3、創建測試方法測試CacheManager
@Test void cacheManagerTest() { // 1. 創建緩存管理器 CacheManager cacheManager = CacheManager.create(this.getClass().getResourceAsStream("/ehcache.xml")); // 2. 獲取ehcache.xml 中定義的 HelloWorldCache 緩存對象 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); System.out.println(value.getObjectKey()); System.out.println(value.getObjectValue()); // 6. 刪除元素 cache.remove("key1"); System.out.println(cache.getSize()); // 7. 刷新緩存 cache.flush(); // 8. 關閉緩存管理器 cacheManager.shutdown(); }
運行結果如下:
[ key = key1, value=value1, version=1, hitCount=1, CreationTime = 1630287141405, LastAccessTime = 1630287141406 ]
key1
value1
0
1、封裝CacheManagerHelper
package com.example.ehcachedemo.helper; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Element; /* * Ehcache 緩存管理對象單例 * 磁盤 + 內存 * */ public class CacheManagerHelper { private final String EHCAHE_PATH = "/ehcache.xml"; private final String CACHE_NAME = "localCache"; private CacheManager manager; private Cache cache; private CacheManagerHelper() { init(); } private static class SingletonInstance { private static final CacheManagerHelper singleton = new CacheManagerHelper(); } public static CacheManagerHelper getInstance() { return SingletonInstance.singleton; } /** * 每次開始使用緩存對象需要初始化 */ public void init() { manager = CacheManager.create(this.getClass().getResourceAsStream(EHCAHE_PATH)); cache = manager.getCache(CACHE_NAME); } /** * 把key放入緩存中 */ public void put(String key, Object value) { cache.put(new Element(key, value)); flush(); } /** * 根據key獲取緩存元素 */ public Object get(String key) { Element element = cache.get(key); return element != null ? element.getObjectValue() : null; } /** * 根據key移除緩存 */ public void remove(String key) { cache.remove(key); flush(); } /** * 構建內存與磁盤的關系 */ public void flush() { cache.flush(); } /** * 關閉緩存管理器 */ public void shutdown() { manager.shutdown(); } }
2、新建User類和UserController測試CacheManagerHelper
User:
package com.example.ehcachedemo.bean; public class User { public User() { } public User(String userId, String name, int age) { this.userId = userId; this.name = name; this.age = age; } private String userId; private String name; private int age; //region getter and setter public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } //endregion @Override public String toString() { return "User{" + "userId='" + userId + '\'' + ", name='" + name + '\'' + ", age=" + this.age + '}'; } }
UserController
package com.example.ehcachedemo.controller; import com.example.ehcachedemo.bean.User; import com.example.ehcachedemo.helper.CacheManagerHelper; import org.springframework.cache.annotation.Cacheable; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping(value = "/user") public class UserController { /** * 通過CacheManagerHelper獲取緩存信息 * @param id 訪問id * */ @GetMapping("byId/{id}") public String getUserById(@PathVariable String id) { String result = (String) CacheManagerHelper.getInstance().get(id); if (result == null) { User user = new User(id, "張三", (int) (Math.random() * 100)); result = user.toString(); CacheManagerHelper.getInstance().put(id, result); } return result; } }
3、封裝EhCacheConfiguration類,用於注解方式使用Ehcache
package com.example.ehcachedemo.configuration; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.ehcache.EhCacheCacheManager; import org.springframework.cache.ehcache.EhCacheManagerFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; @Configuration @EnableCaching public class EhCacheConfiguration { @Bean public EhCacheManagerFactoryBean cacheManagerFactoryBean() { EhCacheManagerFactoryBean bean = new EhCacheManagerFactoryBean(); bean.setConfigLocation(new ClassPathResource("ehcache.xml")); bean.setShared(true); return bean; } @Bean public EhCacheCacheManager ehCacheCacheManager(EhCacheManagerFactoryBean bean) { return new EhCacheCacheManager(bean.getObject()); } }
Application啟動類需要加入 @EnableCaching
4、UserController 注解方式使用EhCache
/** * 通過注解方式,如果緩存存在,則從緩存獲取數據,否則從方法體獲取,並更新到緩存中 * @param id 訪問id * */ @GetMapping("ByIdWithInject/{id}") @Cacheable(value = "localCache", key = "#id") public String getUserByIdWithInject(@PathVariable String id) { User user = new User(id, "張三", (int) (Math.random() * 100)); return user.toString(); }
5、@Cacheable、@CachePut、@CacheEvict的使用
在Service使用@Cacheable、@CachePut、@CacheEvict
package com.example.ehcachedemo.service; import com.example.ehcachedemo.bean.User; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import java.util.Date; @Service @Component public class UserServiceImpl implements UserService { /** * @param userId * @return * @Cacheable: 1.方法運行之前,先去查詢Cache(緩存組件),按照cacheNames指定的名字獲取; * 2.去Cache中查找緩存的內容,使用一個key,默認就是方法的參數; * 3.沒有查到緩存就調用目標方法 * 4.將目標方法返回的結果,放進緩存中 * 5.condition:指定符合條件的情況下才緩存; */ @Cacheable(value = "UserCache", key = "#userId", condition = "#userId!=''") @Override public User findById(String userId) { System.out.println(new Date().getTime() + "進入UserService.findById,當前userId為:" + userId); return new User(userId, "張三", (int) (Math.random() * 100)); } /** * @param user * @return * @CachePut:既調用方法,又更新緩存數據;同步更新緩存 運行時機: * 1、先調用目標方法 * 2、將目標方法的結果緩存起來 */ @CachePut(value = "UserCache", key = "#user.userId") @Override public User updateUser(User user) { System.out.println(new Date().getTime() + "進入UserService.updateUser,當前userId為:" + userId); return new User(userId, "張三", (int) (Math.random() * 100)); } /** * @param userId * @CacheEvict:緩存清除 key:指定要清除的數據 * beforeInvocation = true:代表清除緩存操作是在方法運行之前執行,無論方法是否出現異常,緩存都清除 */ @CacheEvict(value = "UserCache", key = "#userId", beforeInvocation = true) @Override public void deleteUser(String userId) { System.out.println(new Date().getTime() + "進入UserService.deleteUser,當前userId為:" + userId); } }
測試代碼
@Autowired UserService userService; @Test void userTest() throws InterruptedException { // 1.新增 User user1 = new User("123", "張三", (int) (Math.random() * 100)); userService.updateUser(user1); System.out.println("初始:" + user1); // 2.查詢 user1 = userService.findById("123"); System.out.println("查詢:" + user1); // 3.清除 userService.deleteUser("123"); System.out.println("已清除緩存"); // 4.查詢 user1 = userService.findById("123"); System.out.println("查詢:" + user1); System.out.println("休眠10秒后重新查詢"); Thread.sleep(10 * 1000); // 休眠10秒,測試是否過期重新獲取 user1 = userService.findById("123"); System.out.println(user1); }
測試結果
1630291225178進入UserService.updateUser,當前userId為:123
初始:User{userId='123', name='張三', age=82}
查詢:User{userId='123', name='張三', age=82}
1630291225232進入UserService.deleteUser,當前userId為:123
已清除緩存
1630291225232進入UserService.findById,當前userId為:123
查詢:User{userId='123', name='張三', age=21}
休眠10秒后重新查詢
1630291235234進入UserService.findById,當前userId為:123
User{userId='123', name='張三', age=13}
五、參考文檔
https://blog.csdn.net/huang_wei_cai/article/details/105293166
