Caffeine是使用Java8對Guava緩存的重寫版本,在Spring Boot 2.0中將取代Guava。如果出現Caffeine,CaffeineCacheManager將會自動配置。使用spring.cache.cache-names屬性可以在啟動時創建緩存,並可以通過以下配置進行自定義(按順序):
- spring.cache.caffeine.spec: 定義的特殊緩存
- com.github.benmanes.caffeine.cache.CaffeineSpec: bean定義
- com.github.benmanes.caffeine.cache.Caffeine: bean定義
例如,以下配置創建一個foo和bar緩存,最大數量為500,存活時間為10分鍾:
spring.cache.cache-names=foo,bar
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s
除此之外,如果定義了com.github.benmanes.caffeine.cache.CacheLoader,它會自動關聯到CaffeineCacheManager。由於該CacheLoader將關聯被該緩存管理器管理的所有緩存,所以它必須定義為CacheLoader<Object, Object>,自動配置將忽略所有泛型類型。
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
1 @EnableCaching 2 @Configuration 3 public class CaffeineCacheConfig { 4 public static final Integer CAFFEINE_MAXSIZE = PropertyUtil.getInt("caffeine.maxsize", "10000"); 5 public static final Integer CAFFEINE_EXPIRE_TIME = PropertyUtil.getInt("caffeine.expire.time", "3"); 6 7 /** 8 * 創建基於Caffeine的Cache Manager 9 * @return 10 */ 11 @Bean("caffeineCacheManager") 12 public CacheManager CaffeineCacheManager() { 13 CaffeineCacheManager cacheManager = new CaffeineCacheManager(); 14 15 cacheManager.setCaffeine(Caffeine.newBuilder().recordStats() 16 .expireAfterWrite(CAFFEINE_EXPIRE_TIME, TimeUnit.SECONDS) 17 .maximumSize(CAFFEINE_MAXSIZE)); 18 19 return cacheManager; 20 } 21 22 }
使用@EnableCaching注解讓Spring Boot開啟對緩存的支持
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不可以同時使用
3.service
1 @Service("caffeineCacheService") 2 public class CaffeineCacheServiceImpl { 3 @Autowired 4 CacheManager caffeineCacheManager; 5 6 private final static String DEFAULT_CACHE = "default"; 7 8 public <T> T getValue(Object key) { 9 if(key == null) return null; 10 11 Cache cache = caffeineCacheManager.getCache(DEFAULT_CACHE); 12 if(cache != null) { 13 Cache.ValueWrapper wrapper = cache.get(key); 14 if (wrapper != null) 15 return (T) wrapper.get(); 16 } 17 18 return null; 19 } 20 21 public <T> T getValue(String cacheName, Object key) { 22 if(cacheName == null || key == null) return null; 23 24 Cache cache = caffeineCacheManager.getCache(cacheName); 25 if(cache != null) { 26 Cache.ValueWrapper wrapper = cache.get(key); 27 if (wrapper != null) 28 return (T) wrapper.get(); 29 } 30 31 return null; 32 } 33 34 public void putValue(Object key, Object value) { 35 if(key == null || value == null) return; 36 37 Cache cache = caffeineCacheManager.getCache(DEFAULT_CACHE); 38 if(cache != null) { 39 cache.put(key, value); 40 } 41 } 42 43 public void putValue(String cacheName, Object key, Object value) { 44 if(cacheName == null || key == null || value == null) return; 45 46 Cache cache = caffeineCacheManager.getCache(cacheName); 47 if(cache != null) { 48 cache.put(key, value); 49 } 50 } 51 }
其中get(key)只是返回了ValueWrapper,具體value需要get方法。我看了caffeineCacheManager.getCache方法,按理說在cachemap中找不到cache的時候會新建一個cache並放入map中再返回,但是看了源碼方法上標注了@Nullable,為了代碼嚴謹,選擇了判斷null。
4.實例
1 private static Integer uuid = 0; 2 @Cacheable(value = DEFAULT_CACHE, key = "#pin") 3 public Integer getUUid(String pin) { 4 /* 5 if(getValue(pin) != null) { 6 return getValue(pin); 7 }*/ 8 9 return uuid++; 10 }
附:spring cache相關注解介紹 @Cacheable、@CachePut、@CacheEvict
@Cacheable
@Cacheable是用來聲明方法是可緩存的。將結果存儲到緩存中以便后續使用相同參數調用時不需執行實際的方法。直接從緩存中取值。最簡單的格式需要制定緩存名稱。
例如:
1 @Cacheable("books") 2 public Book findBook(ISBN isbn) {...}
在上面的代碼片段中,findBook方法與名為books的緩存想關聯。每次調用該方法時,將在緩存中檢查該請求是否已執行,以免重復執行。雖然在大多數情況下,只有一個緩存被聲明,注釋允許指定多個名稱,以便使用多個緩存。這種情況下,在執行方法之前,每個緩存都會檢查之前執行的方法,只要有一個緩存命中,即直接從緩存中返回相關的值。
即使沒有實際執行緩存方法,所有其他不包含該值的緩存也將被更新。
例如:
1 @Cacheable({"books", "isbns"}) 2 public Book findBook(ISBN isbn) {...}
默認key生成:
默認key的生成按照以下規則:
- 如果沒有參數,則使用0作為key
- 如果只有一個參數,使用該參數作為key
- 如果又多個參數,使用包含所有參數的hashCode作為key
自定義key的生成:
當目標方法參數有多個時,有些參數並不適合緩存邏輯
比如:
1 @Cacheable("books") 2 public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
其中checkWarehouse,includeUsed並不適合當做緩存的key.針對這種情況,Cacheable 允許指定生成key的關鍵屬性,並且支持支持SpringEL表達式。(推薦方法)
再看一些例子:
1 @Cacheable(cacheNames="books", key="#isbn") 2 public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) 3 4 @Cacheable(cacheNames="books", key="#isbn.rawNumber") 5 public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) 6 7 @Cacheable(cacheNames="books", key="T(someType).hash(#isbn)") 8 public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) 9 10 @Cacheable(cacheNames="books", key="#map['bookid'].toString()") 11 public Book findBook(Map<String, Object> map)
緩存的同步 sync:
在多線程環境下,某些操作可能使用相同參數同步調用。默認情況下,緩存不鎖定任何資源,可能導致多次計算,而違反了緩存的目的。對於這些特定的情況,屬性 sync 可以指示底層將緩存鎖住,使只有一個線程可以進入計算,而其他線程堵塞,直到返回結果更新到緩存中。
例:
1 @Cacheable(cacheNames="foos", sync="true") 2 public Foo executeExpensiveOperation(String id) {...}
屬性condition:
有時候,一個方法可能不適合一直緩存(例如:可能依賴於給定的參數)。屬性condition支持這種功能,通過SpEL 表達式來指定可求值的boolean值,為true才會緩存(在方法執行之前進行評估)。
例:
1 @Cacheable(cacheNames="book", condition="#name.length < 32") 2 public Book findBook(String name)
此外,還有一個unless 屬性可以用來是決定是否添加到緩存。與condition不同的是,unless表達式是在方法調用之后進行評估的。如果返回false,才放入緩存(與condition相反)。 #result指返回值 例:
1 @Cacheable(cacheNames="book", condition="#name.length < 32", unless="#result.name.length > 5"") 2 public Book findBook(String name)
@CachePut
如果緩存需要更新,且不干擾方法的執行,可以使用注解@CachePut。@CachePut標注的方法在執行前不會去檢查緩存中是否存在之前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的緩存中。
1 @CachePut(cacheNames="book", key="#isbn") 2 public Book updateBook(ISBN isbn, BookDescriptor descriptor)
注意:應該避免@CachePut 和 @Cacheable同時使用的情況。
@CacheEvict
spring cache不僅支持將數據緩存,還支持將緩存數據刪除。此過程經常用於從緩存中清除過期或未使用的數據。
@CacheEvict要求指定一個或多個緩存,使之都受影響。此外,還提供了一個額外的參數allEntries 。表示是否需要清除緩存中的所有元素。默認為false,表示不需要。當指定了allEntries為true時,Spring Cache將忽略指定的key。有的時候我們需要Cache一下清除所有的元素。
1 @CacheEvict(cacheNames="books", allEntries=true) 2 public void loadBooks(InputStream batch)
清除操作默認是在對應方法成功執行之后觸發的,即方法如果因為拋出異常而未能成功返回時也不會觸發清除操作。使用beforeInvocation可以改變觸發清除操作的時間,當我們指定該屬性值為true時,Spring會在調用該方法之前清除緩存中的指定元素。
1 @CacheEvict(cacheNames="books", beforeInvocation=true) 2 public void loadBooks(InputStream batch)
@CacheConfig
有時候一個類中可能會有多個緩存操作,而這些緩存操作可能是重復的。這個時候可以使用@CacheConfig
1 @CacheConfig("books") 2 public class BookRepositoryImpl implements BookRepository { 3 4 @Cacheable 5 public Book findBook(ISBN isbn) {...} 6 }
@CacheConfig是一個類級別的注解,允許共享緩存的名稱、KeyGenerator、CacheManager 和CacheResolver。
該操作會被覆蓋。
開啟緩存注解
java類配置:
1 @Configuration 2 @EnableCaching 3 public class AppConfig { 4 }
XML 配置:
<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>