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实现一级缓存分享完成,下篇我们会继续分享此种缓存实现的细节,敬请期待!