实现“业务无感缓存”的关键是定义语义清晰的专用注解(如@cacheread、@cacheevict、@cachelock),配合aop切面统一处理缓存逻辑,支持spel动态key、多级缓存ttl、事务一致性、空值缓存与运行时开关调试。

直接用注解声明缓存意图,而不是在方法里写 Redis 操作或手动判断 key 是否存在——这是实现“业务无感缓存”的关键。
定义语义清晰的缓存注解
不复用 @Cacheable 这类泛化注解,而是按实际场景设计专用注解:
-
@CacheRead:标记查询方法,指定缓存名、SpEL 表达式生成的 key(如
"user:#id")、本地与远程缓存过期时间 -
@CacheEvict:标记更新/删除方法,声明需清除的缓存项(支持单 key 或批量,如
{"user:#id", "stats:#tenantId"}) - @CacheLock(可选):自动包裹 Redis 分布式锁,防止高并发下缓存击穿或脏写
注解属性要覆盖真实需求
每个属性都对应一个具体问题:
- key:必须支持 SpEL,能引用方法参数、返回值甚至上下文对象(如当前登录用户 ID)
-
cacheNames:区分不同业务域,避免 key 冲突(例如
cacheNames = "user", "order") - expireSeconds 和 localExpireSeconds:分别控制 Redis 和 Caffeine 的 TTL,本地缓存通常更短
- unless(类似 Spring Cache):允许按返回值条件跳过缓存,比如只缓存非 null 或非空集合结果
切面中统一执行缓存逻辑
注解本身不做事,靠 AOP 切面解析并落地:
- 读操作走“Caffeine → Redis → DB”三级穿透流程,命中即返回,未命中则逐层回填
- 写操作绑定事务生命周期:仅在
TransactionSynchronization.afterCommit()阶段触发失效,避免回滚污染 - 多节点一致性靠 Redisson Pub/Sub 实现——失效时发消息,其他实例监听并清本地缓存
- 空值也缓存(如存
"NULL"字符串),配合布隆过滤器或逻辑删除字段防穿透
运行时可开关、可调试
真正解耦的关键在于可控性:
- 通过配置项(如
cache.enabled=false)一键关闭整个缓存链路,切面直接放行原方法 - 日志埋点记录每次缓存命中/穿透/失效动作,方便线上排查
- 提供管理端点(如
/actuator/cache-status)实时查看各 cacheName 的命中率与大小
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











