@Configuration
public class CaffeineConfig {
public static final String CACHE_PREFIX_ONE = "ONE";
public static final String CACHE_PREFIX_TWO = "TWO";
@Bean("caffeineCacheManager")
public CacheManager caffeineCacheManager(BusinessCacheConfig cacheConfig) {
Caffeine<Object, Object> caffeine = Caffeine.newBuilder()
.initialCapacity(20)
.maximumSize(500)
.expireAfterWrite(cacheConfig.getCache().getCacheDuration(), TimeUnit.SECONDS);
CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
caffeineCacheManager.setAllowNullValues(Boolean.FALSE);
caffeineCacheManager.setCaffeine(caffeine);
caffeineCacheManager.setCacheNames(Lists.newArrayList(CACHE_PREFIX_ONE));
return caffeineCacheManager;
}
/**
* 打卡点列表专用缓存管理
*
* @param cacheConfig 缓存配置
* @return
*/
@Bean("caffeineCacheManagerForPointList")
public CacheManager caffeineCacheManagerForPointList(BusinessCacheConfig cacheConfig) {
Caffeine<Object, Object> caffeine = Caffeine.newBuilder()
.initialCapacity(20)
.maximumSize(500)
.expireAfterWrite(cacheConfig.getCache().getPointListCacheDuration(), TimeUnit.SECONDS);
CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
caffeineCacheManager.setAllowNullValues(Boolean.FALSE);
caffeineCacheManager.setCaffeine(caffeine);
caffeineCacheManager.setCacheNames(Lists.newArrayList(CACHE_PREFIX_TWO));
return caffeineCacheManager;
}
}
这段示例代码是 Spring Boot 中基于 Caffeine 实现的缓存管理器配置,通过@Configuration和@Bean定义了两个不同的 Caffeine 缓存管理器(caffeineCacheManager、caffeineCacheManagerForStationList),核心目的是为不同业务场景提供定制化的本地缓存策略。
先拆解Caffeine.newBuilder()中的关键配置,这是理解 “为什么这么定义” 的基础:
| 配置项 | 作用 |
|---|---|
initialCapacity(20) | 缓存初始容量(预分配空间),减少扩容带来的性能损耗 |
maximumSize(500) | 缓存最大条目数(基于 LRU 算法淘汰超出的条目),防止缓存占用过多内存 |
expireAfterWrite(...) | 写入后过期时间(TTL),缓存条目写入后,超过指定时间自动失效 |
setAllowNullValues(false) | 禁止缓存 null 值,避免空值缓存导致的业务逻辑异常(如判断 null 时误判) |
setCacheNames(...) | 指定缓存名称(缓存分区),不同业务可通过缓存名隔离缓存数据 |
两个 Bean 的核心区别仅在于过期时间:
caffeineCacheManager:使用cacheConfig.getCache().getCacheDuration()(通用缓存过期时间);
caffeineCacheManagerForPointList:使用cacheConfig.getCache().getPointListCacheDuration()(打卡点列表专用过期时间)。
其余配置(初始容量、最大容量、禁止 null 值)完全一致,目的是为 “打卡点列表” 这一高频 / 特殊业务提供独立的缓存策略。
本地缓存的核心是 “适配业务特性”,不同业务对缓存的 “过期时效、内存占用、访问频率” 要求不同,分开定义能精准匹配需求:
@Bean("xxx"))和缓存名,实现缓存数据的物理隔离,避免不同业务的缓存互相干扰(如通用缓存失效不会影响站点列表缓存);BusinessCacheConfig读取(而非硬编码),支持配置文件动态调整,无需修改代码重启服务。initialCapacity(20)预分配空间,减少缓存初始化后频繁扩容的性能开销;maximumSize(500)限制缓存上限,防止缓存无限制增长导致 OOM(内存溢出);expireAfterAccess(访问后过期),更适合 “数据有明确有效期” 的场景;结合代码中 “通用缓存” 和 “站点列表专用缓存” 的设计,列举典型适用场景:
适用于无特殊时效要求、访问频率中等的业务数据,例如:
适用于站点相关、时效要求更精准的业务数据,例如:
除上述业务场景,代码中的 Caffeine 配置还适用于以下通用本地缓存场景:
| 配置项 | 适配场景 |
|---|---|
maximumSize(500) | 缓存条目数有限的场景(如中小型应用的本地缓存,避免内存占用过高) |
initialCapacity(20) | 缓存初始化后会快速填充数据的场景(如服务启动后批量加载基础数据) |
expireAfterWrite | 数据有 “写入后有效期” 的场景(如活动配置、临时促销规则) |
setAllowNullValues(false) | 所有需要 “缓存有效数据、避免空值干扰” 的场景(如查询类业务、数据统计类业务) |
ChargeConstants.CACHE_PREFIX作为缓存名,建议为站点列表单独设置缓存名(如ChargeConstants.STATION_LIST_CACHE),彻底隔离缓存数据;expireAfterAccess;refreshAfterWrite配置(需结合LoadingCache使用);这段代码的核心设计思路是 “按业务场景定制缓存策略”:通过拆分不同的缓存管理器,适配不同业务数据的时效、访问特性;通过配置化的方式让缓存策略可动态调整;通过 Caffeine 的核心配置保证缓存的性能和稳定性。这种设计适用于 “多业务模块、缓存需求差异化” 的后端服务(如充电桩 / 充值类业务系统),是本地缓存在 Spring Boot 中典型的最佳实践。
在 Spring Boot 中配置好 Caffeine 缓存管理器(CacheManager)后,核心使用方式分为注解式(声明式) 和编程式 两种,以下结合代码中两个不同的CacheManager Bean(通用 / 站点列表专用),详细说明使用方法、场景和注意事项。
Spring 提供了@EnableCaching注解来开启缓存功能,必须先在启动类 / 配置类上添加该注解,否则缓存注解不会生效:
@SpringBootApplication
@EnableCaching // 开启缓存功能
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
通过 Spring 缓存注解(@Cacheable/@CachePut/@CacheEvict)结合指定的CacheManager Bean,实现缓存的自动读写 / 失效,无需手动操作缓存 API。
在业务方法上通过cacheManager指定通用缓存管理器,value指定缓存名称(需匹配配置中setCacheNames的缓存名):
@Service
public class CacheConfigService {
// 注入业务依赖(如DAO)
@Autowired
private CacheConfigMapper cacheConfigMapper;
/**
* 查询充值金额档位(通用缓存场景)
* @param type 充值类型
* @return 档位列表
*/
@Cacheable(
value = CacheConstants.CACHE_PREFIX, // 匹配配置中setCacheNames的缓存名
cacheManager = "caffeineCacheManager", // 指定通用缓存管理器Bean名
key = "#type" // 缓存key:用充值类型作为唯一标识
)
public List<CacheGrade> queryCacheGrade(String type) {
// 缓存未命中时执行:查询数据库/其他数据源
return cacheConfigMapper.selectCacheGradeByType(type);
}
/**
* 更新充值档位后,清除对应缓存(避免脏数据)
* @param type 充值类型
*/
@CacheEvict(
value = CacheConstants.CACHE_PREFIX,
cacheManager = "caffeineCacheManager",
key = "#type"
)
public void updateCacheGrade(String type, CacheGrade grade) {
cacheConfigMapper.updateCacheGrade(type, grade);
}
}
同理,通过cacheManager指定专用 Bean 名,适配站点列表业务:
@Service
public class PointService {
@Autowired
private PointMapper PointMapper;
/**
* 查询站点列表(专用缓存场景)
* @param city 城市
* @return 站点列表
*/
@Cacheable(
value = CacheConstants.CACHE_PREFIX, // 若需彻底隔离,建议改为专属缓存名(如CacheConstants.Point_LIST_CACHE)
cacheManager = "caffeineCacheManagerForPointList", // 指定站点专用缓存管理器
key = "#city" // 缓存key:用城市作为唯一标识
)
public List<PointVO> queryPointList(String city) {
// 缓存未命中时执行:查询数据库
return PointMapper.selectPointListByCity(city);
}
/**
* 刷新站点信息后,清除缓存
* @param city 城市
*/
@CacheEvict(
value = CacheConstants.CACHE_PREFIX,
cacheManager = "caffeineCacheManagerForPointList",
key = "#city"
)
public void refreshPointList(String city) {
// 业务逻辑:如同步最新站点数据
}
}
| 参数 | 作用 |
|---|---|
value | 缓存名称(需匹配配置类中setCacheNames的值,实现缓存分区) |
cacheManager | 指定使用的缓存管理器 Bean 名(区分通用 / 站点专用缓存) |
key | 缓存的唯一标识(SpEL 表达式),如#city/#type,避免不同参数的缓存冲突 |
@Cacheable | 缓存查询结果:方法执行前先查缓存,命中则返回;未命中则执行方法并缓存结果 |
@CachePut | 更新缓存:执行方法后,将结果更新到缓存(适用于新增 / 修改场景) |
@CacheEvict | 清除缓存:执行方法后,删除指定 key 的缓存(适用于数据更新 / 删除场景) |
若业务逻辑复杂(如需要手动控制缓存的读写 / 失效),可直接注入CacheManager,手动获取缓存实例操作:
@Service
public class CacheConfigService {
// 注入通用缓存管理器(通过Bean名称指定)
@Autowired
@Qualifier("caffeineCacheManager")
private CacheManager caffeineCacheManager;
public List<CacheGrade> queryCacheGrade(String type) {
// 1. 获取指定名称的缓存实例
Cache cache = caffeineCacheManager.getCache(CacheConstants.CACHE_PREFIX);
// 2. 尝试从缓存中获取数据
Cache.ValueWrapper wrapper = cache.get(type);
if (wrapper != null) {
// 缓存命中:直接返回
return (List<CacheGrade>) wrapper.get();
}
// 3. 缓存未命中:查询数据库
List<CacheGrade> gradeList = cacheConfigMapper.selectCacheGradeByType(type);
// 4. 将结果放入缓存
cache.put(type, gradeList);
return gradeList;
}
// 手动清除缓存
public void clearCacheGradeCache(String type) {
Cache cache = caffeineCacheManager.getCache(CacheConstants.CACHE_PREFIX);
cache.evict(type); // 清除指定key的缓存
// cache.clear(); // 清除该缓存下所有数据(慎用)
}
}
@Service
public class PointService {
// 注入打卡点专用缓存管理器
@Autowired
@Qualifier("caffeineCacheManagerForPointList")
private CacheManager PointListCacheManager;
public List<PointVO> queryPointList(String city) {
Cache cache = PointListCacheManager.getCache(CacheConstants.CACHE_PREFIX);
Cache.ValueWrapper wrapper = cache.get(city);
if (wrapper != null) {
return (List<PointVO>) wrapper.get();
}
List<PointVO> PointList = PointMapper.selectPointListByCity(city);
cache.put(city, PointList);
return PointList;
}
}
CacheManager都设置了相同的CacheConstants.CACHE_PREFIX作为缓存名,建议改为专属缓存名(如通用缓存用Cache_CONFIG_CACHE,打卡点列表用Point_LIST_CACHE),避免不同缓存管理器操作同一批缓存数据,导致策略冲突(如过期时间不一致)。setAllowNullValues(false)已禁止缓存 null 值,但若查询结果为空(如无对应城市的打卡点),需在业务代码中处理(如返回空列表,而非 null),避免每次查询都穿透到数据库。@CacheEvict/cache.evict()),否则会出现 “缓存脏数据”;expireAfterWrite自动过期兜底;若更新频率高,建议主动清除缓存。wrapper.get()返回Object,需强制类型转换,建议增加类型校验(如instanceof),避免类型转换异常。@Qualifier指定 Bean 名称(如@Qualifier("caffeineCacheManagerForPointList")),否则 Spring 会默认注入任意一个CacheManager,导致缓存策略错误。| 使用方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 注解式 | 代码简洁、无侵入、自动管理缓存 | 灵活度低,复杂逻辑难以适配 | 简单查询 / 更新、标准化业务逻辑 |
| 编程式 | 灵活度高,可手动控制缓存全流程 | 代码冗余、需手动处理缓存逻辑 | 复杂业务(如多条件缓存、批量操作) |
配置好CacheManager后,核心是通过@Cacheable等注解(推荐)或手动注入CacheManager操作缓存,关键是通过cacheManager参数 /@Qualifier区分通用和打卡点专用缓存管理器,匹配不同业务的缓存策略(过期时间、容量等)。
实际开发中优先使用注解式,仅在逻辑复杂时用编程式,同时注意缓存隔离、脏数据和穿透问题。