Spring Cache抽象层
约 1403 字大约 5 分钟
springcache
2025-04-06
概述
Spring Cache 提供了一套统一的缓存抽象,通过注解的方式声明式地管理缓存,与具体的缓存实现解耦。开发者可以在不修改业务代码的情况下,切换底层缓存实现(如从 ConcurrentMap 切换到 Redis、Caffeine 等)。本文将详细介绍 Spring Cache 的注解、配置和高级用法。
缓存抽象架构
核心注解
@Cacheable —— 查询缓存
方法执行前先查缓存,命中则直接返回;未命中则执行方法并将结果存入缓存。
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
// 基本用法
@Cacheable(value = "products", key = "#id")
public Product findById(Long id) {
log.info("Loading product from DB: {}", id);
return productRepository.findById(id)
.orElseThrow(() -> new ProductNotFoundException(id));
}
// 自定义key(SpEL表达式)
@Cacheable(value = "products", key = "#category + ':' + #page + ':' + #size")
public Page<Product> findByCategory(String category, int page, int size) {
return productRepository.findByCategory(category, PageRequest.of(page, size));
}
// condition:满足条件才缓存
@Cacheable(value = "products", key = "#id",
condition = "#id > 0")
public Product findByIdWithCondition(Long id) {
return productRepository.findById(id).orElse(null);
}
// unless:结果满足条件则不缓存
@Cacheable(value = "products", key = "#id",
unless = "#result == null || #result.price.compareTo(T(java.math.BigDecimal).ZERO) == 0")
public Product findByIdUnlessZeroPrice(Long id) {
return productRepository.findById(id).orElse(null);
}
// 使用多个缓存名
@Cacheable(value = {"products", "productCache"}, key = "#id")
public Product findByIdMultiCache(Long id) {
return productRepository.findById(id).orElse(null);
}
}@CachePut —— 更新缓存
无论缓存中是否已存在,总是执行方法并将结果更新到缓存。
@CachePut(value = "products", key = "#product.id")
public Product updateProduct(Product product) {
return productRepository.save(product);
}
@CachePut(value = "products", key = "#result.id")
public Product createProduct(CreateProductRequest request) {
Product product = new Product(request);
return productRepository.save(product);
}@CacheEvict —— 清除缓存
// 清除指定key的缓存
@CacheEvict(value = "products", key = "#id")
public void deleteProduct(Long id) {
productRepository.deleteById(id);
}
// 清除该缓存下所有数据
@CacheEvict(value = "products", allEntries = true)
public void clearAllProductCache() {
log.info("All product caches cleared");
}
// beforeInvocation:在方法执行之前清除(默认是执行之后)
@CacheEvict(value = "products", key = "#id", beforeInvocation = true)
public void deleteProductForce(Long id) {
productRepository.deleteById(id);
}@Caching —— 组合操作
@Caching(
cacheable = {
@Cacheable(value = "products", key = "#id")
},
evict = {
@CacheEvict(value = "productList", allEntries = true)
}
)
public Product findAndRefreshList(Long id) {
return productRepository.findById(id).orElse(null);
}
// 同时清除多个缓存
@Caching(evict = {
@CacheEvict(value = "products", key = "#id"),
@CacheEvict(value = "productList", allEntries = true),
@CacheEvict(value = "productStats", allEntries = true)
})
public void deleteProductCascade(Long id) {
productRepository.deleteById(id);
}@CacheConfig —— 类级别配置
@Service
@CacheConfig(cacheNames = "users", keyGenerator = "customKeyGenerator")
public class UserService {
@Cacheable(key = "#id") // 继承类级别的cacheNames
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
@CacheEvict(key = "#id")
public void deleteById(Long id) {
userRepository.deleteById(id);
}
}Key 生成策略
SpEL 表达式
// 方法参数
@Cacheable(key = "#userId")
@Cacheable(key = "#p0") // 第一个参数
@Cacheable(key = "#a0") // 第一个参数
// 对象属性
@Cacheable(key = "#request.userId + ':' + #request.type")
// 方法名
@Cacheable(key = "#root.methodName + ':' + #id")
// 目标类
@Cacheable(key = "#root.targetClass.simpleName + ':' + #id")
// 返回值(仅@CachePut可用)
@CachePut(key = "#result.id")
// 条件表达式
@Cacheable(key = "T(java.lang.String).valueOf(#id)")自定义 KeyGenerator
@Configuration
public class CacheKeyConfig {
@Bean
public KeyGenerator customKeyGenerator() {
return (target, method, params) -> {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getSimpleName());
sb.append(":");
sb.append(method.getName());
for (Object param : params) {
sb.append(":").append(param);
}
return sb.toString();
};
}
}CacheManager 配置
Redis 缓存配置
@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
// 默认配置
RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.disableCachingNullValues();
// 为不同缓存名设置不同的配置
Map<String, RedisCacheConfiguration> cacheConfigs = new HashMap<>();
cacheConfigs.put("products",
defaultConfig.entryTtl(Duration.ofHours(1)));
cacheConfigs.put("users",
defaultConfig.entryTtl(Duration.ofMinutes(10)));
cacheConfigs.put("sessions",
defaultConfig.entryTtl(Duration.ofHours(24)));
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(defaultConfig)
.withInitialCacheConfigurations(cacheConfigs)
.transactionAware()
.build();
}
}Caffeine 本地缓存配置
@Configuration
@EnableCaching
public class CaffeineCacheConfig {
@Bean
public CaffeineCacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(1000)
.expireAfterWrite(Duration.ofMinutes(10))
.recordStats()); // 启用统计
return manager;
}
}多级缓存(本地 + Redis)
@Component
public class MultiLevelCacheManager implements CacheManager {
private final CaffeineCacheManager localCacheManager;
private final RedisCacheManager redisCacheManager;
public MultiLevelCacheManager(CaffeineCacheManager localCacheManager,
RedisCacheManager redisCacheManager) {
this.localCacheManager = localCacheManager;
this.redisCacheManager = redisCacheManager;
}
@Override
public Cache getCache(String name) {
Cache localCache = localCacheManager.getCache(name);
Cache redisCache = redisCacheManager.getCache(name);
return new MultiLevelCache(name, localCache, redisCache);
}
@Override
public Collection<String> getCacheNames() {
Set<String> names = new HashSet<>();
names.addAll(localCacheManager.getCacheNames());
names.addAll(redisCacheManager.getCacheNames());
return names;
}
}自定义 CacheResolver
@Component
public class DynamicCacheResolver implements CacheResolver {
private final CacheManager redisCacheManager;
private final CacheManager caffeineCacheManager;
public DynamicCacheResolver(
@Qualifier("redisCacheManager") CacheManager redisCacheManager,
@Qualifier("caffeineCacheManager") CacheManager caffeineCacheManager) {
this.redisCacheManager = redisCacheManager;
this.caffeineCacheManager = caffeineCacheManager;
}
@Override
public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
Collection<Cache> caches = new ArrayList<>();
// 根据注解上的缓存名称或其他条件动态选择CacheManager
for (String cacheName : context.getOperation().getCacheNames()) {
if (cacheName.startsWith("local:")) {
caches.add(caffeineCacheManager.getCache(cacheName.substring(6)));
} else {
caches.add(redisCacheManager.getCache(cacheName));
}
}
return caches;
}
}
// 使用
@Cacheable(cacheNames = "local:products", cacheResolver = "dynamicCacheResolver")
public Product findByIdLocal(Long id) { ... }
@Cacheable(cacheNames = "products", cacheResolver = "dynamicCacheResolver")
public Product findByIdRedis(Long id) { ... }常见问题
1. 缓存穿透
// 缓存null值防止穿透(Redis配置中不要disableCachingNullValues)
@Cacheable(value = "products", key = "#id",
unless = "#result == null") // 不缓存null,按需决定
public Product findById(Long id) {
return productRepository.findById(id).orElse(null);
}2. 自调用导致缓存失效
与 @Transactional 相同,@Cacheable 基于 AOP 代理实现,同类方法内部调用不经过代理,缓存注解不会生效。
3. 缓存与数据库一致性
// 先更新数据库,再清除缓存
@Transactional
@CacheEvict(value = "products", key = "#product.id")
public Product updateProduct(Product product) {
return productRepository.save(product);
}总结
Spring Cache 通过 @Cacheable、@CachePut、@CacheEvict 三个核心注解提供了声明式缓存管理。CacheManager 抽象使得可以灵活切换 Redis、Caffeine 等缓存实现。通过 SpEL 表达式定制缓存 Key,通过 condition/unless 控制缓存条件。实际项目中常采用本地缓存 + Redis 的多级缓存架构,兼顾性能和一致性。
贡献者
更新日志
2026/3/14 13:09
查看所有更新日志
9f6c2-feat: organize wiki content and refresh site setup于