【Spring】Spring Cache
简介
为在项目中使用缓存技术(例如 Redis),我们通常要将添加缓存的代码耦合到业务代码中,这样每个业务代码都需要添加重复的缓存代码。自然可以想到,使用 Spring AOP 的思想进行解耦。
Spring Cache 就是这么一个框架。它利用了 Spring AOP,实现了基于注解的缓存功能,并且进行了合理的抽象,业务代码不用关心底层是使用了什么缓存框架,只需要简单地加一个注解并配置缓存框架的类型,就能实现缓存功能了。而且 Spring Cache 也提供了很多默认的配置,用户可以为自己的业务代码快速加上一个很不错的缓存功能。
Spring 从3.1开始定义了 org.springframework.cache.Cache
和 org.sprngframework.cache.CacheManager
接口来统一不同的缓存技术,并支持使用 JCache
(JSR-107)注解简化我们的开发。
CacheManager
接口为缓存的组件定义存储规则,其内部的缓存组件(Cache
接口类型)才是真正向缓存中存储数据的(以 k:v 存储数据)。这些组件根据名字进行区分,存储的规则是由 CacheManager
制定的。例如:
- 如果使用
ConcrrentMapCache
类型的CacheManager
,则其内部存储的ConcrrentMapCache
里面都是使用ConcurrentMap
存储数据的,通常用于本地缓存。 - 如果使用
RedisCache
类型的RedisCacheManager
,则其内部存储的RedisCache
对象存储的数据都是存放在 Redis 中的,通常用于分布式缓存。
从图中可以看到,不同的 CacheManager
中保存着不同类型的 Cache
,并且这些 Cache
都有自己的名字(在 Redis 中显示的 Key 的名字),并且其内按照 k:v 的方式保存数据。
以 RedisCache
为例。使用 Spring Cache 时,在方法上标注注解 @Cacheable
。这样每次在调用该方法前会向 Redis 检查该缓存是否已存在:
- 如果已存在就直接从缓存中获取方法调用后的结果,方法内代码将不再被执行
- 如果不存在再执行方法内代码并将方法的返回结果到 Redis 中,下次就可以直接调用从缓存中获取该数据了
原理
引入 Spring Cache 的场景启动器后,其将注入一个 CacheAutoConfiguration
的自动配置类:
该自动配置类将注入一些缓存框架的自动配置类,例如 RedisAutoConfiguration
:
并且 Spring Cache 自动配置了 RedisCacheManager
与 RedisCacheConfiguration
,用于设置 Redis 缓存的各个配置参数。如果容器中有开发人员编写的 RedisCacheConfiguration
,就使用。否则就使用默认提供的配置参数。
若开发人员想自定义缓存的配置,只需要向容器中注入一个自定义的 RedisCacheConfiguration
并在其内设置缓存参数即可,这样这些参数就会应用到当前 RedisCacheManager
管理所有缓存分区中。
快速使用
- 导入 Maven 依赖
1 | <dependency> |
- 在启动类加上
@EnableCaching
注解即可开启使用缓存
1 |
|
- 配置使用 Redis 进行缓存
1 | spring: |
- 在要缓存的方法上面添加
@Cacheable
注解,即可缓存这个方法的返回值
1 |
|
下面详细介绍 Spring Cache 里的常用注解。
常用注解
Spring Cache有几个常用注解,分别为@Cacheable
、@CachePut
、@CacheEvict
、@Caching
、 @CacheConfig
。除了最后一个CacheConfig
外,其余四个都可以用在类上或者方法级别上,如果用在类上,就是对该类的所有public方法生效,下面分别介绍一下这几个注解。
@Cacheable
@Cacheble
注解表示这个方法有了缓存的功能,方法的返回值会被缓存下来,下一次调用该方法前,会去检查是否缓存中已经有值,如果有就直接返回,不调用方法。如果没有,就调用方法,然后把结果缓存起来。这个注解一般用在查询方法上。示例:
1 |
|
该注解的参数:
value
:同cacheNames
。缓存的分区名称。如果不指定key-prefix
时,就会作为缓存名称的前半部分(即前缀)key
:缓存名称。如果不指定key-prefix
时,就会以 value 为前缀拼接在一起,共同组成在 Redis 中的缓存 key 值。例如上例中在 Redis 里保存的缓存数据的key 值为category::getCatalogJson
sync
:为 true 代表开启本地锁(注意不是分布式锁)。默认是不开启的。可用该参数缓解缓存击穿问题。开启后只能指定唯一一个 value。
key 的取值采用 Spring Expression Language (SpEL) 规则,如果想直接取值需要加 ‘xxx’。否则要使用 #root.xxxx 进行动态取值。并且 key 只能写单个值,不可以指定多个 key
注意:当配置文件中不显式指定前缀 key-prefix
时,就会使用该注解中的 value 值作为前缀,这样最终保存在 Redis 里的缓存的 key 就是 value 值(分区名):: key 值(缓存名)
。上例中的两个缓存数据在 Redis 里的显示效果为:
其中 category
和 product
是分区名(value 值),其后的 getCatalogJson
和 getCategoryLevel1
是缓存名(key 值)。
@Cacheable
的默认配置:
- 如果缓存中有,方法就不再调用
key
如果不显式指定,则默认自动生成为:缓存的区分:SimpleKey
- 缓存中存储的值,默认使用 JDK 序列化(若想指定为 JSON 格式需要自定义配置)
- 默认的过期时间:-1(可自定义)
@CachePut
加了 @CachePut
注解的方法,会把方法的返回值立即 put 到缓存里面缓存起来,供其它地方使用。它通常用在新增方法上。双写模式可以使用该注解,要求方法必须有返回值,该注解会将该返回值存到缓存中。
1 |
|
@CacheEvict
使用了 @CacheEvict
注解的方法,会在执行完方法内容后清空指定缓存。一般用在更新或者删除的方法上。失效模式可以使用该注解。方法不需要返回值。
1 | // 注意只能写单个key;并且指定删除某个分区下的所有数据 |
注意:该注解的 key 只能写单个值,不可以指定多个 key,若想同时指定多个 key,则需要使用 @Caching
注解。
allEntries = true
作用:指定删除某个分区下的所有数据
@Caching
Java注解的机制决定了,一个方法上只能有一个相同的注解生效。那有时候可能一个方法会操作多个缓存(这个在删除缓存操作中比较常见,在添加操作中不太常见)。
Spring Cache当然也考虑到了这种情况,@Caching
注解就是用来解决这类情况的,一看它的源码就明白了。
1 | public Caching { |
可以使用该注解组合多个注解,例如:
1 |
|
@CacheConfig
前面提到的四个注解,都是 Spring Cache 常用的注解。每个注解都有很多可以配置的属性。但这几个注解通常都是作用在方法上的,而有些配置可能又是一个类通用的,这种情况就可以使用@CacheConfig
了,它是一个类级别的注解,可以在类级别上配置cacheNames
、keyGenerator
、cacheManager
、cacheResolver
等。
自定义配置缓存参数
若想自定义配置 Spring Cache 的缓存配置,只需要向容器中注入一个自定义的 RedisCacheConfiguration
并在其内设置缓存参数即可,这样这些参数就会应用到当前 RedisCacheManager
管理所有缓存分区中。
需要注意的是,如果不加 @EnableConfigurationProperties(CacheProperties.class)
注解,则该配置类将无法读取配置文件中的相关配置参数。该注解将会使 CacheProperties
对象绑定配置文件中的 cache
配置并使其生效。详细原理见文章 【Spring Boot】Spring Boot2
1 |
|
配置文件:
1 | spring: |