必须用 Callable + 反射,因单纯字符串拼接易重复(如多方法同名),仅靠注解无法感知运行时真实参数值;Callable 携带执行上下文,反射安全提取元数据,支持高精度、防重、动态 key 生成。
核心思路是:用反射提取方法签名与参数值,结合 callable 的执行上下文动态生成 key,避免硬编码、规避重复、支持高精度区分。
为什么必须用 Callable + 反射
单纯拼接字符串容易重复,比如两个不同方法都叫 getOrder;而只依赖注解参数又无法感知运行时真实值(如 userId=1001 和 userId=1002 应该生成不同 key)。Callable 提供了延迟执行的语义,天然携带目标方法、入参、甚至泛型信息;反射则能安全读取这些元数据,不依赖参数命名或顺序。
关键步骤拆解
-
定义自定义注解,例如
@Cacheable(key = "#id + ':detail'"),但底层不直接解析 SpEL,而是提取表达式原始字符串和绑定变量名 -
在 AOP 切面中捕获 Callable 实例,通过
MethodInvocation.getThis()和MethodInvocation.getMethod()获取目标类与方法对象 -
用反射读取实际参数值:遍历
MethodInvocation.getArguments(),对每个参数做类型判断(如 String、Long、DTO),对 DTO 类型递归提取非空字段(跳过 createTime、updateTime 等无关字段) -
构造唯一前缀:组合
class.getSimpleName() + "." + method.getName() + "@" + method.getParameterTypes(),确保同名不同参的方法 key 不重叠 - 拼接业务标识:将清洗后的参数值(如 userId=1001, orderId="ORD-20260526")按固定顺序、统一格式(小写+下划线+转义)拼入,最后加哈希后缀(如 MD5 前 8 位)防超长
防重复与精度保障要点
重复 key 主要来自三类场景:方法重载、参数 null/默认值、DTO 字段冗余。应对方式:
- 重载方法靠
getParameterTypes()区分,例如get(String)和get(Long)生成的前缀完全不同 - null 参数统一替换为
"_null",空字符串替换为"_empty",避免和未传参混淆 - Dubbo 或 Spring MVC 场景下,自动过滤掉
BindingResult、HttpServletRequest等非业务参数 - 对集合类参数(List、Map),只取 size + 首尾元素哈希,不展开全部内容,兼顾精度与 key 长度
一个实际 key 示例
假设调用:OrderService.getOrderDetail(OrderQuery query, Long version),其中 query.userId=1001、query.status="PAID"、version=2L
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
最终生成的 Redis key 类似:
ordsvc.getOrderDetail@Ljava/lang/Long;Lcom/example/OrderQuery;:u1001_sPAID_v2_7a3f9c1e这个 key 具备:服务标识、方法签名、参数类型、清洗后业务值、轻量哈希,全局唯一且可追溯。

















