关键在于唯一性建模:注解提供业务语义(如prefix、tenantId、version),SpEL动态解析真实参数值,配合-parameters编译、参数名发现、URL编码与长度截断、命名空间前缀(如ns:v2:report:uid_1001),确保Redis Key高精度、零冲突、可追溯。

关键不在“拼接”,而在“唯一性建模”——注解参数提供业务语义,反射提取运行时真实值,两者结合才能生成真正高精度、零冲突的 Redis Key。
注解设计要带业务上下文,不能只写静态字符串
只定义 @RedisCache(prefix = "user", key = "id") 是危险的:它把 key 逻辑硬编码进注解值,既无法适配不同参数组合,又容易因命名随意导致跨方法冲突。正确做法是让注解支持 SpEL 表达式,并预留可扩展字段:
-
prefix:固定业务域标识,如
"report:dashboard"或"order:summary",用于隔离缓存空间 -
key:必须是 SpEL 表达式,如
"#userId + ':' + #storeId"或"T(java.util.UUID).randomUUID().toString()",确保动态可变 -
version(可选):显式声明版本号,如
v2,避免因 key 逻辑升级导致旧缓存污染新逻辑 - tenantId(多租户必备):强制要求传入租户标识,防止 A 商家缓存被 B 商家误读
反射获取参数名必须配合调试信息,否则 SpEL 解析会失效
Java 编译默认不保留形参名,LocalVariableTableParameterNameDiscoverer 在无 -parameters 编译参数时只能拿到 arg0, arg1...。这会导致 #userId 找不到对应值,最终 key 变成 "null:1002" 这类错误结果。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 编译时加
-parameters(Maven 中配置<compilerArgs><arg>-parameters</arg></compilerArgs>) - AOP 切面中用
discoverer.getParameterNames(method)获取真实参数名数组 - 构造
EvaluationContext时,用ReflectionUtils.findMethod确保 method 对象准确,再将 args 按名绑定到 context
Key 拼接必须做标准化处理,防乱码、防越界、防注入
直接拼接 prefix + ":" + keyResult 很脆弱。真实场景中,用户 ID 可能含中文、订单号含斜杠、时间戳带小数点——这些都会破坏 Redis 的 key 结构或引发序列化异常。
- 对所有动态段执行
URLEncoder.encode(..., "UTF-8"),再替换%为_(如%E4%BD%A0→_E4_BD_A0) - 限制总长度,超长时用 SHA-256 截取前 16 字节转 hex(避免 key 过长拖慢 Redis 查找)
- 禁止在 SpEL 中使用
#root或#this访问非 POJO 对象(如 HttpServletRequest),防止敏感信息泄露进 key
加一层命名空间路由,让 key 具备可治理性
光靠拼接无法解决“谁在用、何时过期、是否已淘汰”等运维问题。建议在 key 最前端嵌入可解析的元数据头:
- 格式示例:
ns:v2:report:dashboard:uid_1001:store_2002 - 其中
ns表示命名空间前缀,便于 redis-cli 批量 scan 清理 -
v2是注解 version 字段,升级时只需改 version 就自动隔离新旧缓存 - 后续各段严格按注解 prefix + 标准化后的 key 段顺序拼接,中间统一用冒号分隔
不复杂但容易忽略:真正决定 key 唯一性的,从来不是字符长度或哈希算法,而是你能否在任意时刻,仅凭这个 key 反推出它所属的接口、参数组合、租户和版本。做到这点,才算落地了“高精度且绝不重复”。

















