1.引入依賴
<properties>
<caffeine.cache.version>2.7.0</caffeine.cache.version>
</properties>
<dependencies>
<!-- Spring boot Cache-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--for caffeine cache-->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>${caffeine.cache.version}</version>
</dependency>
</dependencies>
2.configuration,配置(可以寫代碼配置,也可以在配置文件設置)
package com.coding.controller; import com.github.benmanes.caffeine.cache.Caffeine; import org.springframework.cache.CacheManager; import org.springframework.cache.caffeine.CaffeineCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.TimeUnit; @Configuration public class CacheConfig { @Bean public CacheManager cacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); cacheManager.setCaffeine(Caffeine.newBuilder() // 設置最后一次寫入后經過固定時間過期 .expireAfterWrite(2, TimeUnit.SECONDS) // 初始的緩存空間大小 .initialCapacity(100) // 緩存的最大條數 .maximumSize(1000)); return cacheManager; } }
或者
spring.cache.caffeine.spec=initialCapacity=50,maximumSize=500,expireAfterWrite=5s
spring.cache.type=caffeine
3.使用@EnableCaching注解讓Spring Boot開啟對緩存的支持
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) @EnableCaching public class AliyunOssApplication { public static void main(String[] args) { SpringApplication.run(AliyunOssApplication.class,args); } }
Caffeine配置說明:
initialCapacity=[integer]: 初始的緩存空間大小 maximumSize=[long]: 緩存的最大條數 maximumWeight=[long]: 緩存的最大權重 expireAfterAccess=[duration]: 最后一次寫入或訪問后經過固定時間過期 expireAfterWrite=[duration]: 最后一次寫入后經過固定時間過期 refreshAfterWrite=[duration]: 創建緩存或者最近一次更新緩存后經過固定的時間間隔,刷新緩存 weakKeys: 打開key的弱引用 weakValues:打開value的弱引用 softValues:打開value的軟引用 recordStats:開發統計功能
注意:
- expireAfterWrite和expireAfterAccess同事存在時,以expireAfterWrite為准。
- maximumSize和maximumWeight不可以同時使用
- weakValues和softValues不可以同時使用
4.使用
package com.coding.controller; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import javax.annotation.Resource; import java.util.Collection; import java.util.HashMap; @Slf4j @Service @CacheConfig(cacheNames = "cacheManager") public class UserInfoServiceImpl implements UserInfoService { @Resource private CacheManager cacheManager; /** * 模擬數據庫存儲數據 */ private HashMap<Integer, UserInfo> userInfoMap = new HashMap<Integer, UserInfo>() { { put(1, new UserInfo()); } }; @Override @CachePut(key = "#userInfo.id") public void addUserInfo(UserInfo userInfo) { log.info("create"); userInfoMap.put(userInfo.getId(), userInfo); } @Override @Cacheable(cacheNames = "getByName") public UserInfo getByName(Integer id) { log.info("get"); Collection<String> cacheNames = cacheManager.getCacheNames(); log.info(cacheNames.toString()); return userInfoMap.get(id); } @Override @CachePut(key = "#userInfo.id") public UserInfo updateUserInfo(UserInfo userInfo) { log.info("update"); if (!userInfoMap.containsKey(userInfo.getId())) { return null; } // 取舊的值 UserInfo oldUserInfo = userInfoMap.get(userInfo.getId()); // 替換內容 if (!StringUtils.isEmpty(oldUserInfo.getAge())) { oldUserInfo.setAge(userInfo.getAge()); } if (!StringUtils.isEmpty(oldUserInfo.getName())) { oldUserInfo.setName(userInfo.getName()); } if (!StringUtils.isEmpty(oldUserInfo.getSex())) { oldUserInfo.setSex(userInfo.getSex()); } // 將新的對象存儲,更新舊對象信息 userInfoMap.put(oldUserInfo.getId(), oldUserInfo); // 返回新對象信息 return oldUserInfo; } @Override @CacheEvict(key = "#id") public void deleteById(Integer id) { log.info("delete"); userInfoMap.remove(id); } }
附:spring cache相關注解介紹 @Cacheable、@CachePut、@CacheEvict
@Cacheable
應用到讀取數據的方法上,即可緩存的方法,如查找方法,先從緩存中讀取,如果沒有再調用相應方法獲取數據,然后把數據添加到緩存中。
該注解主要有下面幾個參數:
- value、cacheNames:兩個等同的參數(cacheNames為Spring 4新增,作為value的別名),用於指定緩存存儲的集合名。由於Spring 4中新增了@CacheConfig,因此在Spring 3中原本必須有的value屬性,也成為非必需項了
- key:緩存對象存儲在Map集合中的key值,非必需,缺省按照函數的所有參數組合作為key值,若自己配置需使用SpEL表達式,比如:@Cacheable(key = "#p0"):使用函數第一個參數作為緩存的key值,更多關於SpEL表達式的詳細內容可參考官方文檔
- condition:緩存對象的條件,非必需,也需使用SpEL表達式,只有滿足表達式條件的內容才會被緩存,比如:@Cacheable(key = "#p0", condition = "#p0.length() < 3"),表示只有當第一個參數的長度小於3的時候才會被緩存。
- unless:另外一個緩存條件參數,非必需,需使用SpEL表達式。它不同於condition參數的地方在於它的判斷時機,該條件是在函數被調用之后才做判斷的,所以它可以通過對result進行判斷。
- keyGenerator:用於指定key生成器,非必需。若需要指定一個自定義的key生成器,我們需要去實現org.springframework.cache.interceptor.KeyGenerator接口,並使用該參數來指定。需要注意的是:該參數與key是互斥的
- cacheManager:用於指定使用哪個緩存管理器,非必需。只有當有多個時才需要使用
- cacheResolver:用於指定使用那個緩存解析器,非必需。需通過org.springframework.cache.interceptor.CacheResolver接口來實現自己的緩存解析器,並用該參數指定。
@Cacheable是用來聲明方法是可緩存的。將結果存儲到緩存中以便后續使用相同參數調用時不需執行實際的方法。直接從緩存中取值。最簡單的格式需要制定緩存名稱。
例如:
1 @Cacheable("books") 2 public Book findBook(ISBN isbn) {...}
在上面的代碼片段中,findBook方法與名為books的緩存想關聯。每次調用該方法時,將在緩存中檢查該請求是否已執行,以免重復執行。雖然在大多數情況下,只有一個緩存被聲明,注釋允許指定多個名稱,以便使用多個緩存。這種情況下,在執行方法之前,每個緩存都會檢查之前執行的方法,只要有一個緩存命中,即直接從緩存中返回相關的值。
即使沒有實際執行緩存方法,所有其他不包含該值的緩存也將被更新。
例如:
@Cacheable({"books", "isbns"}) public Book findBook(ISBN isbn) {...}
默認key生成:
默認key的生成按照以下規則:
- 如果沒有參數,則使用0作為key
- 如果只有一個參數,使用該參數作為key
- 如果又多個參數,使用包含所有參數的hashCode作為key
自定義key的生成:
當目標方法參數有多個時,有些參數並不適合緩存邏輯
比如:
@Cacheable("books") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
其中checkWarehouse,includeUsed並不適合當做緩存的key.針對這種情況,Cacheable 允許指定生成key的關鍵屬性,並且支持支持SpringEL表達式。(推薦方法)
再看一些例子:
@Cacheable(cacheNames="books", key="#isbn") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) @Cacheable(cacheNames="books", key="#isbn.rawNumber") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) @Cacheable(cacheNames="books", key="T(someType).hash(#isbn)") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) @Cacheable(cacheNames="books", key="#map['bookid'].toString()") public Book findBook(Map<String, Object> map)
緩存的同步 sync:
在多線程環境下,某些操作可能使用相同參數同步調用。默認情況下,緩存不鎖定任何資源,可能導致多次計算,而違反了緩存的目的。對於這些特定的情況,屬性 sync 可以指示底層將緩存鎖住,使只有一個線程可以進入計算,而其他線程堵塞,直到返回結果更新到緩存中。
例:
@Cacheable(cacheNames="foos", sync="true") public Foo executeExpensiveOperation(String id) {...}
屬性condition:
有時候,一個方法可能不適合一直緩存(例如:可能依賴於給定的參數)。屬性condition支持這種功能,通過SpEL 表達式來指定可求值的boolean值,為true才會緩存(在方法執行之前進行評估)。
例:
@Cacheable(cacheNames="book", condition="#name.length < 32") public Book findBook(String name)
此外,還有一個unless 屬性可以用來是決定是否添加到緩存。與condition不同的是,unless表達式是在方法調用之后進行評估的。如果返回false,才放入緩存(與condition相反)。 #result指返回值 例:
@Cacheable(cacheNames="book", condition="#name.length < 32", unless="#result.name.length > 5"")
public Book findBook(String name)
@CachePut
如果緩存需要更新,且不干擾方法的執行,可以使用注解@CachePut。@CachePut標注的方法在執行前不會去檢查緩存中是否存在之前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的緩存中。
@CachePut(cacheNames="book", key="#isbn") public Book updateBook(ISBN isbn, BookDescriptor descriptor)
注意:應該避免@CachePut 和 @Cacheable同時使用的情況。
@CacheEvict
spring cache不僅支持將數據緩存,還支持將緩存數據刪除。此過程經常用於從緩存中清除過期或未使用的數據。
除了同@Cacheable一樣的參數之外,@CacheEvict還有下面兩個參數:
- allEntries:非必需,默認為false。當為true時,會移除所有數據
- beforeInvocation:非必需,默認為false,會在調用方法之后移除數據。當為true時,會在調用方法之前移除數據。
@CacheEvict要求指定一個或多個緩存,使之都受影響。此外,還提供了一個額外的參數allEntries 。表示是否需要清除緩存中的所有元素。默認為false,表示不需要。當指定了allEntries為true時,Spring Cache將忽略指定的key。有的時候我們需要Cache一下清除所有的元素。
@CacheEvict(cacheNames="books", allEntries=true) public void loadBooks(InputStream batch)
清除操作默認是在對應方法成功執行之后觸發的,即方法如果因為拋出異常而未能成功返回時也不會觸發清除操作。使用beforeInvocation可以改變觸發清除操作的時間,當我們指定該屬性值為true時,Spring會在調用該方法之前清除緩存中的指定元素
@CacheEvict(cacheNames="books", beforeInvocation=true) public void loadBooks(InputStream batch)
@CacheConfig
有時候一個類中可能會有多個緩存操作,而這些緩存操作可能是重復的。這個時候可以使用@CacheConfig
@CacheConfig("books") public class BookRepositoryImpl implements BookRepository { @Cacheable public Book findBook(ISBN isbn) {...} }
@CacheConfig是一個類級別的注解,允許共享緩存的名稱、KeyGenerator、CacheManager 和CacheResolver。
該操作會被覆蓋。
@Caching
組合多個Cache注解使用。示例:
@Caching( put = { @CachePut(value = "user", key = "#user.id"), @CachePut(value = "user", key = "#user.username"), @CachePut(value = "user", key = "#user.age") } }
緩存策略
如果緩存滿了,從緩存中移除數據的策略,常見的有FIFO, LRU 、LFU
- FIFO (First in First Out) 先進先出策略,即先放入緩存的數據先被移除
- LRU (Least Recently Used) 最久未使用策略, 即使用時間距離現在最久的那個數據被移除
- LFU (Least Frequently Used) 最少使用策略,即一定時間內使用次數(頻率)最少的那個數據被移除
- TTL(Time To Live)存活期,即從緩存中創建時間點開始至到期的一個時間段(不管在這個時間段內有沒被訪問過都將過期)
- TTI (Time To Idle)空閑期,即一個數據多久沒有被訪問就從緩存中移除的時間。
附注
通過@EnableCaching注解自動化配置合適的緩存管理器(CacheManager),Spring Boot根據下面的順序去偵測緩存提供者:
- Generic
- JCache (JSR-107)
- EhCache 2.x
- Hazelcast
- Infinispan
- Redis
- Guava
- Simple
spring.cache.type = xxx
另外可通過注入cacheManager來調試查看使用哪種類型,進一步熟悉cache
@Resource
private CacheManager cacheManager;