SpringBoot项目里,用Caffeine和Spring Cache注解搞定本地缓存(附完整代码)
SpringBoot项目中Caffeine与Spring Cache注解的深度整合实践
在构建高性能Web服务时,缓存技术是提升系统响应速度的关键组件。SpringBoot生态中,Caffeine作为新一代高性能本地缓存库,与Spring Cache抽象层的结合,能够以声明式的方式实现缓存逻辑,显著减少样板代码。本文将深入探讨如何通过@Cacheable、@CachePut和@CacheEvict三大核心注解,配合Caffeine的强大性能,构建优雅的缓存解决方案。
1. 环境准备与基础配置
1.1 依赖引入
首先需要在项目中添加必要的依赖。对于Maven项目,在pom.xml中加入:
<dependencies> <!-- Spring Cache抽象层 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- Caffeine缓存实现 --> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>3.1.8</version> </dependency> </dependencies>注意:Caffeine 3.x版本需要Java 11及以上运行环境,若项目使用Java 8,需选择2.9.x版本。
1.2 缓存配置类
创建缓存配置类来定义Caffeine的具体行为:
@Configuration @EnableCaching public class CacheConfig { @Bean public CacheManager cacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); cacheManager.setCaffeine(Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .initialCapacity(100) .maximumSize(1000) .recordStats()); // 开启统计功能 return cacheManager; } }关键配置参数说明:
| 参数 | 类型 | 说明 | 推荐值 |
|---|---|---|---|
| expireAfterWrite | Duration | 写入后过期时间 | 5-30分钟 |
| initialCapacity | int | 初始缓存大小 | 100-500 |
| maximumSize | long | 最大缓存条目数 | 1000-10000 |
| recordStats | - | 是否记录统计信息 | 生产环境建议开启 |
2. 核心注解实战应用
2.1 @Cacheable注解详解
@Cacheable是最常用的缓存注解,用于标记方法的返回值应当被缓存:
@Service public class ProductService { @Cacheable(value = "products", key = "#id", unless = "#result == null") public Product getProductById(Long id) { // 模拟数据库查询 return productRepository.findById(id).orElse(null); } }注解参数解析:
value/cacheNames:指定缓存名称,对应配置中的缓存区域key:使用SpEL表达式定义缓存键unless:条件表达式,当结果为true时不缓存
常见SpEL表达式示例:
@Cacheable(key = "#user.id + ':' + #type") // 组合键 @Cacheable(key = "T(java.util.UUID).randomUUID().toString()") // 随机键 @Cacheable(key = "#root.methodName + ':' + #id") // 方法名作为键部分2.2 @CachePut更新策略
当需要更新缓存时使用@CachePut注解,它总是会执行方法体,并将结果存入缓存:
@CachePut(value = "products", key = "#product.id") public Product updateProduct(Product product) { return productRepository.save(product); }提示:
@CachePut通常用于更新操作,确保缓存与数据源保持同步。
2.3 @CacheEvict清除策略
删除操作需要同步清除缓存,使用@CacheEvict:
@CacheEvict(value = "products", key = "#id") public void deleteProduct(Long id) { productRepository.deleteById(id); }清除整个缓存区域:
@CacheEvict(value = "products", allEntries = true) public void clearAllProductsCache() { // 无需实现,注解会触发缓存清除 }3. 高级特性与性能优化
3.1 多级缓存策略
结合本地缓存与分布式缓存实现多级缓存:
public class MultiLevelCacheService { @Cacheable(cacheNames = "local", key = "#id") public Product getProductWithMultiCache(Long id) { // 先查本地缓存,未命中则查Redis Product product = redisTemplate.opsForValue().get("product:" + id); if (product == null) { product = productRepository.findById(id).orElse(null); redisTemplate.opsForValue().set("product:" + id, product); } return product; } }3.2 缓存穿透防护
针对缓存穿透问题,可以采用以下策略:
- 空值缓存:缓存null结果,设置较短过期时间
- 布隆过滤器:前置过滤非法请求
- 限流措施:对高频访问进行限制
实现空值缓存示例:
@Cacheable(value = "products", key = "#id", unless = "#result == null") public Product getProductWithNullCache(Long id) { Product product = productRepository.findById(id).orElse(null); if (product == null) { // 记录不存在的信息,防止穿透 return new NullProduct(); } return product; }3.3 缓存性能监控
Caffeine提供了丰富的统计信息,可通过配置开启:
@Bean public CacheManager cacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); cacheManager.setCaffeine(Caffeine.newBuilder() .recordStats()); return cacheManager; }获取统计信息:
CacheStats stats = caffeineCache.stats(); System.out.println("命中率: " + stats.hitRate()); System.out.println("平均加载时间: " + stats.averageLoadPenalty() + "ns");4. 实战案例:用户信息服务
4.1 服务层实现
完整用户服务缓存示例:
@Service @CacheConfig(cacheNames = "users") public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; @Override @Cacheable(key = "#id", unless = "#result == null") public User getUserById(Long id) { return userRepository.findById(id).orElse(null); } @Override @CachePut(key = "#user.id") public User updateUser(User user) { return userRepository.save(user); } @Override @CacheEvict(key = "#id") public void deleteUser(Long id) { userRepository.deleteById(id); } @Override @Caching(evict = { @CacheEvict(key = "#userId"), @CacheEvict(cacheNames = "user-roles", key = "#userId") }) public void updateUserRoles(Long userId, List<Long> roleIds) { // 更新用户角色关系 } }4.2 控制器层集成
RESTful接口示例:
@RestController @RequestMapping("/api/users") public class UserController { @Autowired private UserService userService; @GetMapping("/{id}") public ResponseEntity<User> getUser(@PathVariable Long id) { User user = userService.getUserById(id); return ResponseEntity.ok(user); } @PutMapping public ResponseEntity<User> updateUser(@RequestBody User user) { return ResponseEntity.ok(userService.updateUser(user)); } @DeleteMapping("/{id}") public ResponseEntity<Void> deleteUser(@PathVariable Long id) { userService.deleteUser(id); return ResponseEntity.noContent().build(); } }4.3 复杂缓存策略
对于关联数据的缓存处理:
public class OrderService { @Cacheable(value = "orders", key = "#orderId") public Order getOrderWithUser(Long orderId) { Order order = orderRepository.findById(orderId).orElse(null); if (order != null) { // 触发用户缓存 User user = userService.getUserById(order.getUserId()); order.setUser(user); } return order; } }在实际项目中,根据业务场景合理设计缓存键和过期策略,可以显著提升系统性能。Caffeine与Spring Cache的结合,既保持了代码的简洁性,又能获得接近手动缓存控制的灵活性。
