Spring 缓存
2020-04-03
平时在项目中总是手动的管理缓存,多出来不少的冗余代码不说,在管理某个资源的缓存保存、更新和清除时容易出现差错,借助 Spring Cache 的注解,在某种程度上可以做到优雅地管理缓存。
Spring 缓存关键注解
@Cacheable
: 缓存填充。@CacheEvict
: 缓存清除。@CachePut
: 不影响方法执行的情况下更新缓存。@Caching
: 支持一组缓存操作。@CacheConfig
: Class 级别的缓存设置。
@Cacheable
1 |
|
缓存的填充、清除和更新时需根据缓存的名称和 key
来确定某一个缓存,在 @Cacheable
中支持 SpEL 表达式。可以自定义 keyGenerator
来自定义 key
的生成,但需要注意的是 key
和 keyGenerator
是互斥的,同样,cacheManager
和 cacheResolver
也是互斥的。
缓存条件主要使用 unless
和 condition
参数设置,前者过滤结果,后者过滤条件。
不缓存查询结果为空的数据:
1 |
|
支持 java.util.Optional
的情况 :
1 |
|
注意 result 可能为空,需要另加处理。
SpEL 表达式例子:
Name | Location | Description | Example |
---|---|---|---|
methodName |
Root object | The name of the method being invoked | #root.methodName |
method |
Root object | The method being invoked | #root.method.name |
target |
Root object | The target object being invoked | #root.target |
targetClass |
Root object | The class of the target being invoked | #root.targetClass |
args |
Root object | The arguments (as array) used for invoking the target | #root.args[0] |
caches |
Root object | Collection of caches against which the current method is executed | #root.caches[0].name |
Argument name | Evaluation context | Name of any of the method arguments. If the names are not available (perhaps due to having no debug information), the argument names are also available under the #a<#arg> where #arg stands for the argument index (starting from 0 ). |
#iban or #a0 (you can also use #p0 or #p<#arg> notation as an alias). |
result |
Evaluation context | The result of the method call (the value to be cached). Only available in unless expressions, cache put expressions (to compute the key ), or cache evict expressions (when beforeInvocation is false ). For supported wrappers (such as Optional ), #result refers to the actual object, not the wrapper. |
#result |
@CachePut
在不影响方法执行的基础上更新缓存,用法与 @Cachable
一致。
1 |
|
需要注意的是不要与 @Cachable
一起使用,原因如下:
Using
@CachePut
and@Cacheable
annotations on the same method is generally strongly discouraged because they have different behaviors. While the latter causes the method execution to be skipped by using the cache, the former forces the execution in order to execute a cache update. This leads to unexpected behavior and, with the exception of specific corner-cases (such as annotations having conditions that exclude them from each other), such declarations should be avoided. Note also that such conditions should not rely on the result object (that is, the#result
variable), as these are validated up-front to confirm the exclusion.
@CacheEvict
1 |
|
allEntries
为 true
时,可以清除该 cacheName
下的所有缓存。默认是在方法执行之后清除 ,也可以使用 beforeInvocation
指定之前。
@Caching
批量缓存操作。
1 |
|
@CacheConfig
对某个类中的缓存执行设置。
1 |
|
存在的问题
- Spring Cache 有时候会出现奇怪的行为,比如在指定
key
为#username
后,会一直提示这个参数为空,这个时候使用#p0
可以获取到值,还有一种方法是在@EnableCaching
中设置mode
为ASPECTJ
,而不使用PROXY
。但这个时候可能会报错,说没有ASPECTJ
的包,这时候需要手动把spring-aspects
加入依赖(需注意版本一致)。不幸的是,在 JDK 11当中执行了以上操作,依然不起效果,目前我并没有找到解决方法,只能先用PROXY
。(可能的原因是: Spring recommends that you only annotate concrete classes (and methods of concrete classes) with the@Cache*
annotation, as opposed to annotating interfaces. You certainly can place the@Cache*
annotation on an interface (or an interface method), but this works only as you would expect it to if you use interface-based proxies. The fact that Java annotations are not inherited from interfaces means that, if you use class-based proxies (proxy-target-class="true"
) or the weaving-based aspect (mode="aspectj"
), the caching settings are not recognized by the proxying and weaving infrastructure, and the object is not wrapped in a caching proxy.) - 第二个问题,这些注解在嵌套使用时只会有一个注解生效(我记不清楚是外层还是内层),所以最好避免这样使用方式。