SpringBoot?集成Caffeine實(shí)現(xiàn)一級(jí)緩存及常遇到場(chǎng)景
SpeingBoot 集成Caffeine實(shí)現(xiàn)一級(jí)緩存使我們經(jīng)常遇到的場(chǎng)景。今天我們具體分享一下:
首先 Caffeine 作為一級(jí)緩存,它是 Spring 5.x 默認(rèn)的本地緩存實(shí)現(xiàn),性能優(yōu)于 Guava Cache,且支持過(guò)期時(shí)間設(shè)置。緩存執(zhí)行的流程圖如下:

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 配置參數(shù):
expireAfterWrite:寫入后多久過(guò)期
expireAfterAccess:最后訪問(wèn)后多久過(guò)期
maximumSize:緩存的最大元素?cái)?shù)量
weakKeys/weakValues:使用弱引用,支持垃圾回收
**/
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)//可以選多種時(shí)間
.maximumSize(10));//最大緩存?zhèn)€數(shù)
// 配置特定緩存(超時(shí)時(shí)間5分鐘,到時(shí)間自動(dòng)置為null)
cacheManager.setCacheNames(java.util.Arrays.asList("timeoutParam"));
return cacheManager;
}
}1)配置多個(gè)緩存
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
// 配置不同的緩存區(qū)域(可根據(jù)業(yè)務(wù)需求設(shè)置不同的超時(shí)時(shí)間)
cacheManager.setCaches(Arrays.asList(
new CaffeineCache("timeoutParams",
Caffeine.newBuilder()
.expireAfterWrite(5, TimeUnit.MINUTES) // 5分鐘過(guò)期
.maximumSize(100)
.build()
),
new CaffeineCache("longTermCache",
Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.HOURS) // 1小時(shí)過(guò)期
.maximumSize(1000)
.build()
)
));
return cacheManager;
}不同緩存數(shù)據(jù)的超時(shí)時(shí)間可能不一樣,因此需要設(shè)置不同的緩存。
3、業(yè)務(wù)層實(shí)現(xiàn)
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CachePut;
import org.springframework.stereotype.Service;
@Service
public class MyService {
// 獲取參數(shù)(從緩存讀取,若無(wú)則執(zhí)行方法并緩存結(jié)果)unless:條件表達(dá)式,滿足條件則不緩存(如 #result == null)
@Cacheable(value = "timeoutParams", key = "#paramName")
public String getParam(String paramName) {
// 模擬從數(shù)據(jù)庫(kù)或其他數(shù)據(jù)源獲取
System.out.println("從數(shù)據(jù)源加載參數(shù): " + paramName);
return loadParamFromDB(paramName);
}
// 更新參數(shù)(強(qiáng)制刷新緩存,即使緩存存在)
@CachePut(value = "timeoutParams", key = "#paramName")
public String updateParam(String paramName, String newValue) {
// 保存到數(shù)據(jù)庫(kù)
saveParamToDB(paramName, newValue);
return newValue;
}
// 清除緩存:調(diào)用此方法后,指定緩存鍵將被刪除
@CacheEvict(value = "timeoutParams", key = "#paramName")
public void clearParam(String paramName) {
System.out.println("清除緩存: " + paramName);
// 通常無(wú)需方法體,僅用于觸發(fā)緩存清除
}
// 模擬數(shù)據(jù)庫(kù)操作
private String loadParamFromDB(String paramName) {
// 實(shí)際項(xiàng)目中從數(shù)據(jù)庫(kù)或其他數(shù)據(jù)源獲取
return "默認(rèn)值";
}
private void saveParamToDB(String paramName, String value) {
// 實(shí)際項(xiàng)目中保存到數(shù)據(jù)庫(kù)
}
}4、控制層調(diào)用
// 第一次調(diào)用,會(huì)從數(shù)據(jù)源加載
String value1 = myService.getParam("testParam");
System.out.println("第一次獲取: " + value1);
// 第二次調(diào)用,會(huì)從緩存獲取
String value2 = myService.getParam("testParam");
System.out.println("第二次獲取: " + value2);5、通過(guò)key獲取數(shù)據(jù)
1)通過(guò)key手動(dòng)插入
@Service
public class ManualCacheService {
@Autowired
private CacheManager cacheManager;
public void initCacheManually(String key, Object value) {
Cache cache = cacheManager.getCache("paramCache");
if (cache != null) {
cache.put(key, value); // 手動(dòng)放入緩存
System.out.println("手動(dòng)初始化緩存: " + key + " = " + value);
}
}
}2)手動(dòng)獲取
@Service
public class CacheAccessService {
@Autowired
private 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、通過(guò)注解獲取數(shù)據(jù)(注意偽代碼)
@Service
public class JTServiceImpl {
@Service
public void someMethod() {
// 自調(diào)用 - 導(dǎo)致緩存失效
String token = this.getGlobalToken("token123");
}
@Cacheable(value = "timeoutGlobalToken", key = "#globalToken")
public String getGlobalToken(String globalToken) {
// 實(shí)際獲取token的邏輯
}
}注意:注解失效原因及解決方案
原因分析:
Spring AOP 工作原理:
Spring 的緩存功能(包括
@Cacheable)是基于 AOP 代理實(shí)現(xiàn)的當(dāng)調(diào)用
@Cacheable方法時(shí),實(shí)際調(diào)用的是代理對(duì)象的方法,不是原始對(duì)象的方法
自調(diào)用問(wèn)題(Self-Invocation):
當(dāng)同一個(gè)類中的方法 A 直接調(diào)用方法 B(帶
@Cacheable注解)時(shí)調(diào)用發(fā)生在原始對(duì)象內(nèi)部,繞過(guò)了 Spring 代理
導(dǎo)致
@Cacheable注解完全失效
解決方案:將緩存方法移到另一個(gè)Service中
// 新建專門處理緩存的服務(wù)
@Service
public class TokenCacheService {
@Cacheable(value = "timeoutGlobalToken", key = "#globalToken")
public String getGlobalToken(String globalToken) {
// 實(shí)際獲取token的邏輯
return fetchTokenFromSource(globalToken);
}
private String fetchTokenFromSource(String globalToken) {
// 從數(shù)據(jù)庫(kù)/API獲取token的實(shí)現(xiàn)
}
}調(diào)用方:
// 原服務(wù)調(diào)用緩存服務(wù)
@Service
public class JTServiceImpl {
@Autowired
private TokenCacheService tokenCacheService;
public void businessMethod() {
// 跨類調(diào)用 - 觸發(fā)緩存
String token = tokenCacheService.getGlobalToken("token123");
}
}這是一個(gè)比較常用的解決方案。
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實(shí)現(xiàn)一級(jí)緩存分享完成,下篇我們會(huì)繼續(xù)分享此種緩存實(shí)現(xiàn)的細(xì)節(jié),敬請(qǐng)期待!
到此這篇關(guān)于SpringBoot 集成Caffeine實(shí)現(xiàn)一級(jí)緩存的文章就介紹到這了,更多相關(guān)SpringBoot Caffeine一級(jí)緩存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)文件變化監(jiān)聽代碼實(shí)例
這篇文章主要介紹了Java實(shí)現(xiàn)文件變化監(jiān)聽代碼實(shí)例,通過(guò)定時(shí)任務(wù),輪訓(xùn)查詢文件的最后修改時(shí)間,與上一次進(jìn)行對(duì)比,如果發(fā)生變化,則說(shuō)明文件已經(jīng)修改,進(jìn)行重新加載或?qū)?yīng)的業(yè)務(wù)邏輯處理,需要的朋友可以參考下2024-01-01
java中double強(qiáng)制轉(zhuǎn)換int引發(fā)的OOM問(wèn)題記錄
這篇文章主要介紹了java中double強(qiáng)制轉(zhuǎn)換int引發(fā)的OOM問(wèn)題記錄,本文給大家分享問(wèn)題排查過(guò)程,感興趣的朋友跟隨小編一起看看吧2024-10-10
如何通過(guò)maven最快搭建Springboot項(xiàng)目
因?yàn)镾pringboot項(xiàng)目的啟動(dòng)依賴是通過(guò)pom.xml文件實(shí)現(xiàn)的,所以我們直接創(chuàng)建項(xiàng)目后,調(diào)整pom.xml即可快速開發(fā),這篇文章給大家介紹如何通過(guò)maven最快搭建Springboot項(xiàng)目,感興趣的朋友一起看看吧2025-05-05
Spring注解@Profile實(shí)現(xiàn)開發(fā)環(huán)境/測(cè)試環(huán)境/生產(chǎn)環(huán)境的切換
在進(jìn)行軟件開發(fā)過(guò)程中,一般會(huì)將項(xiàng)目分為開發(fā)環(huán)境,測(cè)試環(huán)境,生產(chǎn)環(huán)境。本文主要介紹了Spring如何通過(guò)注解@Profile實(shí)現(xiàn)開發(fā)環(huán)境、測(cè)試環(huán)境、生產(chǎn)環(huán)境的切換,需要的可以參考一下2023-04-04
很簡(jiǎn)單的Java斷點(diǎn)續(xù)傳實(shí)現(xiàn)原理
這篇文章主要以實(shí)例的方式為大家詳細(xì)介紹了簡(jiǎn)單的Java斷點(diǎn)續(xù)傳實(shí)現(xiàn)原理,感興趣的小伙伴們可以參考一下2016-07-07
Spring?ComponentScan的掃描過(guò)程解析
這篇文章主要介紹了spring?ComponentScan的掃描過(guò)程解析,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03
泛談Java中的不可變數(shù)據(jù)結(jié)構(gòu)
開發(fā)人員通常認(rèn)為擁有final引用,或者val在Kotlin或Scala中,足以使對(duì)象不可變。這篇博客文章深入研究了不可變引用和不可變數(shù)據(jù)結(jié)構(gòu),下面小編來(lái)和大家一起學(xué)習(xí)它2019-05-05
Java中CyclicBarrier和CountDownLatch的用法與區(qū)別
CyclicBarrier和CountDownLatch這兩個(gè)工具都是在java.util.concurrent包下,并且平時(shí)很多場(chǎng)景都會(huì)使用到。本文將會(huì)對(duì)兩者進(jìn)行分析,記錄他們的用法和區(qū)別,感興趣的可以了解一下2021-08-08

