SpeingBoot 集成Caffeine實現一級緩存使我們經常遇到的場景。今天我們具體分享一下:
首先?Caffeine?作為一級緩存,它是 Spring 5.x 默認的本地緩存實現,性能優于 Guava Cache,且支持過期時間設置。緩存執行的流程圖如下:
1、pom文件引入包
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId><version>2.9.3</version> <!-- 兼容 Spring Boot 2.3.5 的版本 --></dependency>
2、換成配置類
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.concurrent.TimeUnit;@Configuration
@EnableCaching
public class CacheConfig {/**Caffeine 配置參數:expireAfterWrite:寫入后多久過期expireAfterAccess:最后訪問后多久過期maximumSize:緩存的最大元素數量weakKeys/weakValues:使用弱引用,支持垃圾回收**/@Beanpublic CacheManager cacheManager() {CaffeineCacheManager cacheManager = new CaffeineCacheManager();cacheManager.setCaffeine(Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES)//可以選多種時間.maximumSize(10));//最大緩存個數// 配置特定緩存(超時時間5分鐘,到時間自動置為null)cacheManager.setCacheNames(java.util.Arrays.asList("timeoutParam"));return cacheManager;}
}
1)配置多個緩存
@Beanpublic CacheManager cacheManager() {SimpleCacheManager cacheManager = new SimpleCacheManager();// 配置不同的緩存區域(可根據業務需求設置不同的超時時間)cacheManager.setCaches(Arrays.asList(new CaffeineCache("timeoutParams",Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES) // 5分鐘過期.maximumSize(100).build()),new CaffeineCache("longTermCache",Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS) // 1小時過期.maximumSize(1000).build())));return cacheManager;}
不同緩存數據的超時時間可能不一樣,因此需要設置不同的緩存。?
3、業務層實現
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CachePut;
import org.springframework.stereotype.Service;@Service
public class MyService {// 獲取參數(從緩存讀取,若無則執行方法并緩存結果)unless:條件表達式,滿足條件則不緩存(如 #result == null)@Cacheable(value = "timeoutParams", key = "#paramName")public String getParam(String paramName) {// 模擬從數據庫或其他數據源獲取System.out.println("從數據源加載參數: " + paramName);return loadParamFromDB(paramName);}// 更新參數(強制刷新緩存,即使緩存存在)@CachePut(value = "timeoutParams", key = "#paramName")public String updateParam(String paramName, String newValue) {// 保存到數據庫saveParamToDB(paramName, newValue);return newValue;}// 清除緩存:調用此方法后,指定緩存鍵將被刪除@CacheEvict(value = "timeoutParams", key = "#paramName")public void clearParam(String paramName) {System.out.println("清除緩存: " + paramName);// 通常無需方法體,僅用于觸發緩存清除}// 模擬數據庫操作private String loadParamFromDB(String paramName) {// 實際項目中從數據庫或其他數據源獲取return "默認值";}private void saveParamToDB(String paramName, String value) {// 實際項目中保存到數據庫}
}
?4、控制層調用
// 第一次調用,會從數據源加載String value1 = myService.getParam("testParam");System.out.println("第一次獲取: " + value1);// 第二次調用,會從緩存獲取String value2 = myService.getParam("testParam");System.out.println("第二次獲取: " + value2);
5、通過key獲取數據
1)通過key手動插入
@Service
public class ManualCacheService {@Autowiredprivate CacheManager cacheManager;public void initCacheManually(String key, Object value) {Cache cache = cacheManager.getCache("paramCache");if (cache != null) {cache.put(key, value); // 手動放入緩存System.out.println("手動初始化緩存: " + key + " = " + value);}}
}
2)手動獲取
@Service
public class CacheAccessService {@Autowiredprivate CacheManager cacheManager;public Object getValueManually(String key) {Cache cache = cacheManager.getCache("paramCache");if (cache != null) {Cache.ValueWrapper wrapper = cache.get(key);return wrapper != null ? wrapper.get() : null;}return null;}
}
?
6、通過注解獲取數據(注意偽代碼)
@Service
public class JTServiceImpl {@Servicepublic void someMethod() {// 自調用 - 導致緩存失效String token = this.getGlobalToken("token123"); }@Cacheable(value = "timeoutGlobalToken", key = "#globalToken")public String getGlobalToken(String globalToken) {// 實際獲取token的邏輯}
}
注意:注解失效原因及解決方案
原因分析:
-
Spring AOP 工作原理:
-
Spring 的緩存功能(包括?
@Cacheable
)是基于 AOP 代理實現的 -
當調用?
@Cacheable
?方法時,實際調用的是代理對象的方法,不是原始對象的方法
-
-
自調用問題(Self-Invocation):
-
當同一個類中的方法 A 直接調用方法 B(帶?
@Cacheable
?注解)時 -
調用發生在原始對象內部,繞過了 Spring 代理
-
導致?
@Cacheable
?注解完全失效
-
解決方案:將緩存方法移到另一個Service中
// 新建專門處理緩存的服務
@Service
public class TokenCacheService {@Cacheable(value = "timeoutGlobalToken", key = "#globalToken")public String getGlobalToken(String globalToken) {// 實際獲取token的邏輯return fetchTokenFromSource(globalToken);}private String fetchTokenFromSource(String globalToken) {// 從數據庫/API獲取token的實現}
}
調用方:
// 原服務調用緩存服務
@Service
public class JTServiceImpl {@Autowiredprivate TokenCacheService tokenCacheService;public void businessMethod() {// 跨類調用 - 觸發緩存String token = tokenCacheService.getGlobalToken("token123");}
}
這是一個比較常用的解決方案。?
?8、獲取所有緩存信息
@GetMapping("/cache/param")
public Map<Object, Object> getCacheEntries() {Cache cache = cacheManager.getCache("paramCache");if (cache == null) {return Collections.emptyMap();}com.github.benmanes.caffeine.cache.Cache<Object, Object> nativeCache = (com.github.benmanes.caffeine.cache.Cache<Object, Object>) cache.getNativeCache();return new HashMap<>(nativeCache.asMap());
}
到此,SpringBoot 集成Caffeine實現一級緩存分享完成,下篇我們會繼續分享此種緩存實現的細節,敬請期待!