@Cacheable 的键是怎么生成的?会不会不同方法互相冲突?null 会不会被缓存?并发未命中时会不会重复加载?异常时 @CacheEvict 还清不清?这篇在 Spring Framework 7.0.9 上逐项验证。
无参数:缓存里的键 [SimpleKey []] 一个参数 one(7):键 [7](键的类型 Integer) 两个参数 two(1, "x"):键 [SimpleKey [1, x]] 同一个缓存 "a" 里同时放无参方法和 one(7) 的结果 → 键 [7, SimpleKey []] key="#x + ':' + #y" 的 spel(2, "y"):键 [2:y] key="#root.methodName + #id" 的 named(3):键 [named3] 键冲突:one(1) 先调用(目标方法执行 1 次),缓存 "a" 里键 1 对应 v1;再调另一个方法 inner(1)(同一个缓存 "a"、同样的单个参数 1):inner 的目标方法执行 0 次,返回的是 v1(拿到了 one(1) 的缓存值)
默认键由参数决定,不含方法名;可以用 SpEL 自定义。
返回 null 的方法,没有 unless:同一个调用连续 2 次,目标方法执行 1 次;缓存里键 [0];有 unless="#result == null":同一个调用连续 2 次,目标方法执行 2 次(null 不缓存) condition="#id > 10":id=5 → 同一个调用连续 2 次,目标方法执行 2 次;id=50 → 同一个调用连续 2 次,目标方法执行 1 次
null 默认也被缓存,unless 与 condition 控制是否缓存。
没有 sync:8 个线程同时取同一个 key(加载要 200 ms),目标方法执行了 8 次,全部返回花了 207 ms sync = true:8 个线程同时取同一个 key(加载要 200 ms),目标方法执行了 1 次,全部返回花了 203 ms
sync = true 让并发未命中的请求只加载一次。
one(5) 缓存后 @CachePut put(5):缓存值 put5(@CachePut 总会执行方法并覆盖缓存) @CacheEvict 之后 键 [] allEntries 之后 键 [] 方法抛异常时 @CacheEvict(默认 beforeInvocation=false):evictFails(8) 之后键 [8](缓存没清掉) beforeInvocation = true:evictFailsBefore(8) 之后键 []
@CachePut 总会执行并覆盖,@CacheEvict 在方法抛异常时默认不清除。
缓存了一个可变 List:第一次拿到后往里 add 一个元素,第二次拿到 [a, b, 被调用者改过](同一个对象:true;目标方法执行 1 次)
进程内缓存存的是引用,调用方改动会污染缓存。
通过 callInner(同一个类里调 inner)连调 2 次:目标方法执行 2 次(缓存没生效);直接调 inner 连调 2 次:目标方法执行 1 次(第 1 次未命中,第 2 次命中)
内部调用不经过代理,缓存不生效。