北美5G网络必备:用Wireshark抓包实战解析CMAS紧急警报(SIB8)
2026/4/30 20:47:33
Spring Cache不是一个具体的缓存实现(它不是 Redis,也不是 EhCache),而是一套缓存抽象层(Cache Abstraction)。
它类似于 JDBC 之于数据库。
@Cacheable),你可以底层换 Redis、Caffeine 或 ConcurrentMap,而业务代码不需要修改一行。redisTemplate.get/set。Spring Cache 主要通过以下 5 个注解控制缓存行为:
| 注解 | 核心作用 | 执行逻辑 (通俗版) | 适用场景 |
|---|---|---|---|
@EnableCaching | 总开关 | 开启 Spring 的缓存代理功能。 | 启动类 / 配置类 |
@Cacheable | 查/存 | 1. 先查缓存,有则直接返回。2. 无则执行方法,并将结果存入缓存。 | 查询(Get) |
@CachePut | 改/存 | 始终执行方法,并将返回值强制更新到缓存中。 | 新增/修改(Save/Update) |
@CacheEvict | 删 | 执行方法(前或后),从缓存中删除指定数据。 | 删除(Delete) |
@Caching | 组合 | 在一个方法上叠加多个操作(如删 A 缓存同时删 B 缓存)。 | 复杂联动场景 |
所有注解(除开关外)都支持以下参数:
value/cacheNames:缓存名称(对应 Redis 的 Key 前缀)。key:缓存 Key,支持SpEL 表达式(如#id,#user.name)。condition:事前判断。满足条件才处理缓存(例如:#id > 10)。unless:事后判断。满足条件不缓存(例如:#result == null)。以下模拟一个用户系统,展示如何结合 Redis 使用。
需求:查询用户,缓存有直接返回,无则查库并回填。同时防止缓存null值。
@ServicepublicclassUserService{@AutowiredprivateUserMapperuserMapper;/** * value = "users": 对应 Redis 前缀 "users::" (或自定义的 "users:") * key = "#id": 取参数 id 作为后缀 * unless = "#result == null": 如果查不到数据,不要把 null 存进 Redis */@Cacheable(value="users",key="#id",unless="#result == null")publicUsergetUserById(Longid){System.out.println("--- 走数据库查询 ---");returnuserMapper.selectById(id);}}需求:修改用户信息后,清理缓存,保证下次查询获取最新数据。
/** * 更新完成后,删除对应的缓存 Key * key 必须与查询时的 key 生成规则一致 */@CacheEvict(value="users",key="#user.id")publicvoidupdateUser(Useruser){userMapper.updateById(user);}/** * 场景:删除用户,或者清空整个缓存桶 * allEntries = true: 删除 users 下的所有 key */@CacheEvict(value="users",allEntries=true)publicvoidclearAllCache(){System.out.println("清空用户缓存");}需求:根据多个参数组合生成 Key。
/** * 假设 type=VIP, page=1 * Redis Key: user_list:VIP_1 */@Cacheable(value="user_list",key="#type + '_' + #page")publicList<User>getUsersByType(Stringtype,intpage){returnuserMapper.selectByType(type,page);}这是 Spring Cache 最经典的“大坑”。
在同一个 Service 类内部,方法 A 调用带@Cacheable的方法 B,方法 B 的缓存注解失效,每次都会执行 SQL。
Spring Cache 基于代理模式 (Proxy Pattern)。
this.method()是目标对象自己调用自己,绕过了 Proxy 对象,所以 AOP 切面无法执行。@ServicepublicclassUserService{// 方法 A:批量查询publicList<User>getUsersByIds(List<Long>ids){List<User>users=newArrayList<>();for(Longid:ids){// 【失效原因】:这里等同于 this.getUserById(id)// 直接调用了类内部的方法,没有经过 Spring 的代理类users.add(getUserById(id));}returnusers;}// 方法 B:单查(注解在此)@Cacheable(value="users",key="#id")publicUsergetUserById(Longid){System.out.println("查询数据库...");returnnewUser(id,"name");}}通过注入自身(代理对象)来调用,强行经过代理层。
@ServicepublicclassUserService{// 1. 注入自己 (加上 @Lazy 防止循环依赖报错)@Autowired@LazyprivateUserServiceself;publicList<User>getUsersByIds(List<Long>ids){List<User>users=newArrayList<>();for(Longid:ids){// 2. 【修复】:使用代理对象 self 调用,而不是 this// 流程:self -> Proxy -> 检查缓存 -> Targetusers.add(self.getUserById(id));}returnusers;}@Cacheable(value="users",key="#id")publicUsergetUserById(Longid){// ...}}将缓存方法抽离到另一个 Service 中,符合单一职责原则。
// 新服务:专门负责缓存操作@ServicepublicclassUserCacheService{@Cacheable(value="users",key="#id")publicUsergetUserById(Longid){...}}// 原服务:注入上面的服务@ServicepublicclassUserService{@AutowiredprivateUserCacheServiceuserCacheService;publicList<User>getUsersByIds(List<Long>ids){// 外部调用,缓存必然生效returnids.stream().map(userCacheService::getUserById).collect(Collectors.toList());}}