Spring原生@Cacheable无timeToLive属性,不支持per-key过期;真正可控方案只有手动用RedisTemplate设TTL或配置多个RedisCacheManager Bean。

@Cacheable 根本没有 timeToLive 属性
你写的 @Cacheable(timeToLive = 60) 会直接编译失败——Spring 原生注解压根没这个字段。IDE 自动补全、某些过时博客或拼凑的 Demo 都可能误导你,但 Spring Framework 源码里 @Cacheable 接口定义中确实不存在任何与 TTL 相关的参数。
Spring Cache 抽象层不支持 per-key 过期语义
Spring Cache 是个通用抽象,背后可以是 ConcurrentHashMap、Caffeine 或 Redis。它只定义了“存”和“取”,不规定“什么时候失效”。Redis 的 EXPIRE 是具体实现能力,但 CacheManager 接口没暴露该语义。所以即使你用的是 Redis,@Cacheable 也只走统一的 entryTtl() 配置,无法根据参数、返回值或业务逻辑动态决定某个 key 过多久过期。
cacheNames 不是 TTL 路由开关,而是命名空间标识
有人试图用 @Cacheable(cacheNames = "user#300") 然后靠自定义 RedisCacheManager 解析 # 后数字来设 TTL,这属于“曲线救国”。但它本质是绕开了 Spring Cache 设计,靠字符串拆分伪造多配置效果。问题在于:
- cacheNames 本意是逻辑分组,不是配置载体;强行塞语义会让 cacheName 变得不可读、难维护
- 所有缓存操作仍共享同一个底层连接池和序列化器,只是配置分支多了
- 如果多个地方都用了
"user#300",它们会共用同一套 RedisCache 实例,看似“不同名”,实则无隔离
真正可控的路径只有两条
要么放弃 @Cacheable 的声明式便利,用 RedisTemplate 手动控制:
-
redisTemplate.opsForValue().set(key, value)+redisTemplate.expire(key, 300, TimeUnit.SECONDS) - 注意:别用
StringRedisTemplate.opsForValue().set(key, value, timeout, unit),它调的是 SETEX,在 key 已存在时不会重置 TTL - 必须检查
expire()返回值,false表示 key 不存在,说明前面 set 失败或被覆盖了
要么接受设计约束,为不同 TTL 需求配多个 RedisCacheManager Bean,并在 @Cacheable 中显式指定 cacheManager = "shortLivedCacheManager"。这时每个 manager 对应独立的 entryTtl() 和连接池,但要注意连接数膨胀风险。
最常被忽略的一点:无论选哪条路,RedisTemplate 的 valueSerializer 必须和读取端一致,否则哪怕 key 存进去了,取出来也是 null 或乱码——这不是过期问题,是序列化错位。


















