还在重复编写冗长的缓存代码?让Caffeine帮你彻底解放生产力。
在Java应用开发中,本地缓存是提升性能的利器,但传统的缓存实现往往存在诸多问题:代码冗余、内存泄漏风险、并发访问性能差等。今天,我们将介绍如何通过Caffeine这一高性能本地缓存框架,彻底标准化本地缓存的书写格式。
一、为什么需要标准化本地缓存?
1.1 传统本地缓存的痛点
在引入Caffeine之前,许多开发者习惯使用HashMap或ConcurrentHashMap实现简易缓存,但这存在明显问题:
- 无自动过期机制:容易导致内存泄漏
- 缺乏淘汰策略:缓存数据只增不减,内存占用无限增长
- 并发性能瓶颈:高并发场景下性能急剧下降
- 代码重复:每个缓存都需要重复实现一样逻辑
1.2 Caffeine的优势
Caffeine是基于Java 8的高性能本地缓存库,在Spring Boot 2.x及以上版本中已成为默认的本地缓存实现。其核心优势包括:
- 高性能:采用Window-TinyLFU算法,命中率比Guava Cache提高10%-20%
- 丰富策略:支持基于大小、时间、引用等多种淘汰维度
- 并发卓越:基于分段锁与无锁CAS操作,单机QPS可达百万级
- 监控完善:内置命中率统计等监控指标
二、Spring Boot与Caffeine快速集成
2.1 添加依赖
第一,在pom.xml中添加依赖:
<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>3.1.8</version>
</dependency>
Spring Boot 2.x及以上版本默认集成了Caffeine,因此无需额外引入Caffeine核心依赖。
2.2 配置缓存管理器
创建标准化配置类,统一管理缓存行为:
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.recordStats());
return cacheManager;
}
}
这段配置定义了标准化的缓存行为:
- initialCapacity(100):初始容量,避免频繁扩容
- maximumSize(1000):最大缓存条目数,防止内存溢出
- expireAfterWrite(10, TimeUnit.MINUTES):写入后10分钟过期
- recordStats():开启统计功能,便于监控
三、标准化缓存注解的使用
3.1 @Cacheable – 核心缓存注解
@Service
public class ProductService {
@Cacheable(value = "products", key = "#id", unless = "#result == null")
public Product getProductById(Long id) {
// 模拟数据库查询
return productRepository.findById(id);
}
}
- value = “products”:指定缓存名称,对应配置中的缓存区
- key = “#id”:使用方法的id参数作为缓存key
- unless = “#result == null”:当结果不为null时才缓存
3.2 条件化缓存的高级用法
@Cacheable(value = "products", key = "#productId",
condition = "#productId > 1000",
unless = "#result.stock == 0")
public Product getProductDetail(Long productId, String category) {
// 只有productId大于1000且库存不为0时才缓存
return productRepository.findDetailById(productId);
}
3.3 @CachePut – 更新缓存
@CachePut(value = "products", key = "#product.id")
public Product updateProduct(Product product) {
Product updated = productRepository.update(product);
return updated;
}
@CachePut每次都会执行方法,并将结果存入缓存,适用于更新操作。
3.4 @CacheEvict – 删除缓存
@CacheEvict(value = "products", key = "#id")
public void deleteProduct(Long id) {
productRepository.delete(id);
}
// 清空products缓存下的所有条目
@CacheEvict(value = "products", allEntries = true)
public void refreshAllProducts() {
// 一般不包含实际业务逻辑,仅用于清空缓存
}
四、多场景缓存配置标准化
4.1 按业务场景定制化配置
在实际项目中,不同业务场景需要不同的缓存策略:
@Configuration
@EnableCaching
public class MultiCacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
// 定义商品缓存:大容量,短期过期
cacheManager.registerCustomCache("products",
Caffeine.newBuilder()
.maximumSize(2000)
.expireAfterWrite(30, TimeUnit.MINUTES)
.build());
// 定义用户信息缓存:长期有效
cacheManager.registerCustomCache("users",
Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterAccess(2, TimeUnit.HOURS)
.build());
// 定义配置信息缓存:小容量,长期有效
cacheManager.registerCustomCache("configs",
Caffeine.newBuilder()
.maximumSize(100)
.expireAfterWrite(24, TimeUnit.HOURS)
.build());
return cacheManager;
}
}
4.2 基于权重的缓存配置
对于值对象大小差异较大的场景,可以使用权重控制:
@Bean
public CacheManager weightedCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.registerCustomCache("largeObjects",
Caffeine.newBuilder()
.maximumWeight(10000)
.weigher((String key, LargeObject value) -> value.getSize())
.expireAfterWrite(1, TimeUnit.HOURS)
.build());
return cacheManager;
}
注意:maximumSize和maximumWeight不可以同时使用。
五、实战案例:电商平台缓存标准化
5.1 商品服务完整示例
@Service
public class StandardProductService {
@Cacheable(value = "products", key = "#id")
public Product getProduct(Long id) {
log.info("查询数据库商品ID: {}", id);
return productRepository.findById(id).orElse(null);
}
@CachePut(value = "products", key = "#product.id")
public Product updateProduct(Product product) {
log.info("更新商品并刷新缓存: {}", product.getId());
return productRepository.save(product);
}
@CacheEvict(value = "products", key = "#id")
public void deleteProduct(Long id) {
log.info("删除商品并清除缓存: {}", id);
productRepository.deleteById(id);
}
@Cacheable(value = "products", key = "#pageable.pageNumber")
public Page<Product> getProductPage(Pageable pageable) {
log.info("查询商品分页: {}", pageable.getPageNumber());
return productRepository.findAll(pageable);
}
}
5.2 缓存监控与统计
@Service
public class CacheMonitorService {
@Autowired
private CacheManager cacheManager;
public void printCacheStats() {
CaffeineCache caffeineCache = (CaffeineCache) cacheManager.getCache("products");
com.github.benmanes.caffeine.cache.stats.CacheStats stats =
caffeineCache.getNativeCache().stats();
log.info("缓存命中率: {}", stats.hitRate());
log.info("平均加载时间: {}", stats.averageLoadPenalty());
log.info("缓存命中数: {}", stats.hitCount());
log.info("缓存未命中数: {}", stats.missCount());
}
}
六、避免常见陷阱
6.1 同一类内方法调用缓存失效问题
@Service
public class ProblematicService {
// 这种方法调用不会触发缓存
public Product getProductWithDetail(Long id) {
// 直接调用内部方法,@Cacheable不会生效
return getProduct(id);
}
@Cacheable(value = "products", key = "#id")
public Product getProduct(Long id) {
return productRepository.findById(id).orElse(null);
}
}
// 正确解决方案:将缓存方法拆分到不同类中
@Service
public class ProductCacheService {
@Cacheable(value = "products", key = "#id")
public Product getProduct(Long id) {
return productRepository.findById(id).orElse(null);
}
}
@Service
public class ProductFacadeService {
@Autowired
private ProductCacheService productCacheService;
// 目前缓存会正常生效
public Product getProductWithDetail(Long id) {
return productCacheService.getProduct(id);
}
}
6.2 缓存空值处理
@Cacheable(value = "products", key = "#id",
unless = "#result == null")
public Product getProductOrDefault(Long id) {
Product product = productRepository.findById(id).orElse(null);
return product != null ? product : createDefaultProduct();
}
七、高级特性与性能优化
7.1 异步缓存刷新
@Bean
public CacheManager asyncRefreshCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.registerCustomCache("products",
Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.refreshAfterWrite(1, TimeUnit.MINUTES)
.build(new CacheLoader<String, Product>() {
@Override
public Product load(String key) {
return productRepository.findById(Long.parseLong(key));
}
}));
return cacheManager;
}
7.2 多级缓存架构
在实际高并发场景中,一般采用多级缓存架构:
- Caffeine作为一级缓存(应用内存级别,速度最快)
- Redis作为二级缓存(分布式缓存,数据共享)
- 数据库作为最终数据源
@Service
public class MultiLevelCacheService {
@Autowired
private RedisTemplate<String, Product> redisTemplate;
@Cacheable(value = "products", key = "#id")
public Product getProductWithMultiLevel(Long id) {
// 1. 第一尝试从Caffeine获取(自动处理)
// 2. 如果Caffeine不存在,尝试从Redis获取
Product product = redisTemplate.opsForValue().get("product:" + id);
if (product != null) {
return product;
}
// 3. 如果Redis也不存在,从数据库获取
product = productRepository.findById(id).orElse(null);
if (product != null) {
redisTemplate.opsForValue().set("product:" + id, product, 1, TimeUnit.HOURS);
}
return product;
}
}
八、总结
通过标准化Caffeine缓存框架的使用,我们能够:
- 统一缓存代码风格,减少重复代码
- 提升系统性能,通过高命中率缓存降低数据库压力
- 避免内存泄漏,通过合理的过期策略和淘汰机制
- 简化维护成本,通过注解和配置聚焦管理缓存行为
Caffeine作为当前性能最优的Java本地缓存库,与Spring Boot的深度集成使得标准化缓存开发变得更加简单。遵循本文的标准化实践,你的项目将获得更高效的缓存实现,同时代码质量与可维护性也将显著提升。
记住,好的缓存设计不仅是提升性能的手段,更是系统架构中不可或缺的重大组成部分。开始使用标准化的Caffeine缓存,告别冗余代码,拥抱高效开发!
© 版权声明
文章版权归作者所有,未经允许请勿转载。

统一缓存框架Caffeine标准化本地缓存实践
大佬带带我👏
继续加油💪
大神💪
真不戳💪
学到了💪
收藏了,感谢分享