缓存
Spring Boot 提供了统一的缓存抽象(Spring Cache),通过注解驱动,屏蔽底层缓存实现(Caffeine、Redis、EhCache 等),业务代码无需感知具体缓存技术。
快速开始
引入依赖并开启缓存:
<!-- 本地缓存:Caffeine -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<!-- 分布式缓存:Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>@SpringBootApplication
@EnableCaching // 开启缓存,底层通过 AOP 代理实现
public class Application {}缓存注解本质上是 AOP 切面,与方法执行时的代理机制相同,自调用(同类内部调用)无效。
核心注解
@Cacheable — 读取缓存
方法执行前先查缓存,命中则直接返回,未命中才执行方法并将结果存入缓存:
@Service
public class UserService {
// 缓存名 "users",key 为方法参数 id
@Cacheable(value = "users", key = "#id")
public User getById(Long id) {
return userRepository.findById(id).orElseThrow();
}
// 多条件 key
@Cacheable(value = "search", key = "#keyword + '-' + #page")
public List<User> search(String keyword, int page) {
return userRepository.search(keyword, page);
}
// 条件缓存:只缓存 id > 0 的结果
@Cacheable(value = "users", key = "#id", condition = "#id > 0")
public User getByIdConditional(Long id) { ... }
// 排除缓存:结果为 null 时不缓存
@Cacheable(value = "users", key = "#id", unless = "#result == null")
public User getNullable(Long id) { ... }
}@CachePut — 更新缓存
总是执行方法,并将结果写入缓存(不会短路)。用于更新操作:
@CachePut(value = "users", key = "#user.id")
public User update(User user) {
return userRepository.save(user);
}@CacheEvict — 删除缓存
// 删除单条
@CacheEvict(value = "users", key = "#id")
public void delete(Long id) {
userRepository.deleteById(id);
}
// 清空整个缓存
@CacheEvict(value = "users", allEntries = true)
public void clearAll() { ... }
// 方法执行前删除(beforeInvocation = true,即使方法异常也会删除)
@CacheEvict(value = "users", key = "#id", beforeInvocation = true)
public void deleteBeforeMethod(Long id) { ... }@Caching — 组合多个缓存操作
@Caching(
cacheable = @Cacheable(value = "users", key = "#id"),
evict = {
@CacheEvict(value = "userList", allEntries = true),
@CacheEvict(value = "userSearch", allEntries = true)
}
)
public User getAndClearList(Long id) { ... }@CacheConfig — 类级别默认配置
@Service
@CacheConfig(cacheNames = "users") // 类内所有方法默认使用 "users" 缓存
public class UserService {
@Cacheable(key = "#id") // 无需重复指定 value
public User getById(Long id) { ... }
}Key 表达式
Spring Cache 使用 SpEL 生成 key:
| 表达式 | 说明 |
|---|---|
#id | 参数名 |
#p0、#a0 | 第 0 个参数 |
#user.id | 参数对象的属性 |
#result | 方法返回值(@CachePut/@CacheEvict 可用) |
#root.method.name | 方法名 |
#root.target.class.name | 目标类名 |
自定义 Key 生成器:
@Bean
public KeyGenerator customKeyGenerator() {
return (target, method, params) ->
target.getClass().getSimpleName() + ":" + method.getName() + ":" +
Arrays.stream(params).map(Object::toString).collect(Collectors.joining(","));
}
// 使用自定义生成器
@Cacheable(value = "users", keyGenerator = "customKeyGenerator")
public User getById(Long id) { ... }缓存实现选型
Caffeine(本地缓存,推荐单机场景)
spring:
cache:
type: caffeine
caffeine:
spec: maximumSize=1000,expireAfterWrite=10m按缓存名单独配置:
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.registerCustomCache("users",
Caffeine.newBuilder()
.maximumSize(500)
.expireAfterWrite(10, TimeUnit.MINUTES)
.recordStats()
.build());
manager.registerCustomCache("config",
Caffeine.newBuilder()
.maximumSize(100)
.expireAfterWrite(1, TimeUnit.HOURS)
.build());
return manager;
}Redis(分布式缓存,推荐集群场景)
spring:
cache:
type: redis
redis:
time-to-live: 10m # 全局 TTL
use-key-prefix: true
key-prefix: "app:"Redis 连接配置参见 Redis集成。
自定义序列化(默认 JDK 序列化,可读性差):
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory factory) {
ObjectMapper om = new ObjectMapper();
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
RedisSerializer<Object> serializer = new GenericJackson2JsonRedisSerializer(om);
RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer))
.disableCachingNullValues();
// 按缓存名定制 TTL
Map<String, RedisCacheConfiguration> configs = new HashMap<>();
configs.put("users", defaultConfig.entryTtl(Duration.ofMinutes(30)));
configs.put("config", defaultConfig.entryTtl(Duration.ofHours(24)));
return RedisCacheManager.builder(factory)
.cacheDefaults(defaultConfig)
.withInitialCacheConfigurations(configs)
.build();
}两级缓存(本地 + 分布式)
本地缓存速度快,Redis 保证多实例一致性,结合使用效果最佳:
@Bean
public CacheManager twoLevelCacheManager(RedisConnectionFactory factory) {
// 可通过 CompositeCacheManager 组合多个 CacheManager
CompositeCacheManager composite = new CompositeCacheManager(
caffeineCacheManager(),
redisCacheManager(factory)
);
composite.setFallbackToNoOpCache(false);
return composite;
}缓存穿透、击穿、雪崩
| 问题 | 场景 | 应对方案 |
|---|---|---|
| 穿透 | 大量请求查询不存在的数据,缓存未命中直达数据库 | 缓存空值(unless 条件去掉 null 判断);布隆过滤器前置拦截 |
| 击穿 | 热点 key 过期瞬间,大量并发请求同时击穿到数据库 | 互斥锁重建;逻辑过期(后台异步刷新) |
| 雪崩 | 大量 key 同时过期,数据库压力骤增 | TTL 加随机偏移;永不过期 + 异步刷新;限流降级 |
缓存穿透防护示例:
@Cacheable(value = "users", key = "#id", unless = "false") // 允许缓存 null
public User getById(Long id) {
return userRepository.findById(id).orElse(null);
// 返回 null 也会被缓存,防止穿透
}