从 3.1 开始,Spring 引入了对 Cachee 的支持。其使用方法和原理都类似于 Spring 对事务管理的支持。Spring Cache 是作用在方法上的,其核心思想是这样当我们在调用一个缓存方法时会把该方法参数和返回结果作为个键值对存放在缓存中,等到下次利用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回。
springboot 自身提供缓存支持,具体实现可以用 redis,ehcache,guava cache,map 来实现,本文采用 guava。
1. guava 配置
首先在pom文件中引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>25.1-jre</version>
</dependency>
其次在springboot配置文件中加入缓存的配置项:
spring:
cache:
type: guava
如果要让缓存生效,则要在 springboot 启动类加入注解:
@EnableCaching
类似于使用数据库,redis 等工具,使用cache也需要进行配置,springboot 配置类如下:
@Configuration
public class SpringCacheConfig {
/**
* spring缓存配置,使用guava
* @return
*/
@Bean
public CacheManager cacheManager(){
GuavaCacheManager cacheManager = new GuavaCacheManager();
cacheManager.setCacheBuilder(CacheBuilder.newBuilder().expireAfterWrite(3, TimeUnit.SECONDS));
return cacheManager;
}
}
在配置cacheManager时可以选择缓存的属性,例如过期时间、最大容量、刷新时间等等。
2. 用 @Cacheable 缓存数据
在使用缓存的时候,只需要在需要缓存的方法上加入注解即可:
/**
* @param subjectId 学科 ID
* @description: 卷标关系缓存
* @author: zcq
* @date: 2021/1/12 下午5:59
*/
@Override
@Cacheable(value = "queryPaperTagRelations", key = "'queryPaperTagRelations'.concat({#subjectId})")
public List<PaperTagRelation> queryPaperTagRelations(Integer subjectId) throws RestClientException {
PaperTagRelationDto tagRelationDto = new PaperTagRelationDto();
tagRelationDto.setSubjectId(subjectId);
Result result = questionRestClient.queryPaperTagList(tagRelationDto);
if (result == null || !result.getSuccess() || result.getData() == null) {
return new ArrayList<>();
}
List<PaperTagRelation> paperTagRelationList = JSONArray.parseArray(JSON.toJSONString(result.getData()), PaperTagRelation.class);
return paperTagRelationList;
}
vlaue
指定 cache 名称,表示方法的执行结果保存到哪个缓存里去,可以理解为为这个方法建立的一个 map 作为缓存,value 就是 map 的名字;
表示当前条缓存的 key,该属性支持 SpringEL 表达式。当我们没有指定该属性时,Spring 将使用默认策略生成 key。可使用「方法名+参数」进行拼接保证唯一,同 map 中的 key;
- 可以直接使用
#参数名
或者#p参数index
- 可以直接使用
- key 的默认策略:默认的 key 生成策略是通过 KeyGenerator 生成的,其默认策略如下:
- n 如果方法没有参数,则使用 0 作为 key。
- n 如果只有一个参数的话则使用该参数作为 key。
- n 如果参数多余一个的话则使用所有参数的 hashCode 作为 key。 ```java @Cacheable(value=”users”, key=”#id”) public User find(Integer id) { returnnull; }
@Cacheable(value=”users”, key=”#p0”) public User find(Integer id) { returnnull; }
@Cacheable(value=”users”, key=”#user.id”) public User find(User user) { returnnull; }
@Cacheable(value=”users”, key=”#p0.id”) public User find(User user) { returnnull; }
- `sync = true`: 默认为 `false`,在使用 guava 作为缓存工具时可以设置为 true 防止缓存击穿。即缓存过期的那一瞬间,如果有很多请求过来,那么这些请求就都会越过缓存区查询数据库,直到查询到了结果缓存才会重新生效。sync 设置为 true时,在这种情况下只会有一个请求去进行后续操作,其余请求会等待缓存生效,这就有效的降低了实际并发量。
<a name="Mkmmo"></a>
#### 用 root 对象生成 key
除了上述使用方法参数作为 key 之外,Spring还为我们提供了一个 root 对象可以用来生成 key。通过该 root 对象我们可以获取到以下信息:
| 属性名称 | 描述 | 示例 |
| --- | --- | --- |
| methodName | 当前方法名 | #root.methodName |
| method | 当前方法 | #root.method.name |
| target | 当前被调用的对象 | #root.target |
| targetClass | 当前被调用的对象的class | #root.targetClass |
| args | 当前方法参数组成的数组 | #root.args[0] |
| caches | 当前被调用的方法使用的Cache | #root.caches[0].name |
当我们要使用 root 对象的属性作为 key 时我们也可以将 `#root` 省略,因为 Spring 默认使用的就是 root 对象的属性。如:
```java
@Cacheable(value={"users", "xxx"}, key="caches[1].name")
public User find(User user) {
return null;
}
condition 属性指定发生的条件
有的时候我们可能并不希望缓存一个方法所有的返回结果。通过 condition 属性可以实现这一功能。condition 属性默认为空,表示将缓存所有的调用情形。其值是通过 SpringEL 表达式来指定的,当为 true 时表示进行缓存处理;当为 false 时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。
如下示例表示只有当 user 的 id 为偶数时才会进行缓存。
@Cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0")
public User find(User user) {
System.out.println("find user by user " + user);
return user;
}
3. 用 @CachePut 更新缓存
在支持 Spring Cache 的环境下,对于使用 @Cacheable
标注的方法,Spring 在每次执行前都会检查 Cache 中是否存在相同 key 的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。
@CachePut
也可以声明一个方法支持缓存功能。与@Cacheable
不同的是使用 @CachePut
标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
// @CachePut 也可以标注在类上和方法上。使用 @CachePut 时我们可以指定的属性跟 @Cacheable 是一样的。
@CachePut("users")// 每次都会执行方法,并将结果存入指定的缓存中
public User find(Integer id) {
returnnull;
}
4. 用 @CacheEvict 使缓存失效
@CacheEvict
是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。
@CacheEvict可以指定的属性有 value、key、condition、allEntries 和 beforeInvocation。其中 value、key 和 condition 的语义与 @Cacheable
对应的属性类似。即:
- value 表示清除操作是发生在哪些Cache上的(对应Cache的名称);
- key 表示需要清除的是哪个 key,如未指定则会使用默认策略生成的 key;
- condition 表示清除操作发生的条件。
下面我们来介绍一下新出现的两个属性 allEntries
和 beforeInvocation
allEntries 属性
allEntries
是 boolean 类型,表示是否需要清除缓存中的所有元素。默认为 false,表示不需要。当指定了 allEntries 为 true时,Spring Cache 将忽略指定的 key。
有的时候我们需要 Cache 一下清除所有的元素,这比一个一个清除元素更有效率。
@CacheEvict(value="users", allEntries=true)
public void delete(Integer id) {
System.out.println("delete user by id: " + id);
}
beforeInvocation 属性
清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。
使用 beforeInvocation
可以改变触发清除操作的时间,当我们指定该属性值为 true 时,Spring 会在调用该方法之前清除缓存中的指定元素。
@CacheEvict(value="users", beforeInvocation=true)
public void delete(Integer id) {
System.out.println("delete user by id: " + id);
}
示例:
/**
* @param token config 里配置的 token
* @description: 清楚卷标关系缓存
* @author: zcq
* @date: 2021/1/12 下午7:18
*/
@Override
@CacheEvict(value = "queryPaperTagRelations", allEntries = true)
public Result deletePaperTags(String token) {
return Result.buildSuccessResult("clear cache successfully");
}
5. @Caching
@Caching
注解可以让我们在一个方法或者类上同时指定多个 Spring Cache 相关的注解。其拥有三个属性:cacheable
、put
和evict
,分别用于指定 @Cacheable
、@CachePut
和@CacheEvict
。
@Caching(cacheable = @Cacheable("users"), evict = { @CacheEvict("cache2"), @CacheEvict(value = "cache3", allEntries = true) })
public User find(Integer id) {
returnnull;
}
6. 自定义注解
Spring 允许我们在配置可缓存的方法时使用自定义的注解,前提是自定义的注解上必须使用对应的注解进行标注。如我们有如下这么一个使用 @Cacheable
进行标注的自定义注解。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Cacheable(value="users")
public @interface MyCacheable {
}
那么在我们需要缓存的方法上使用 @MyCacheable
进行标注也可以达到同样的效果。
@MyCacheable
public User findById(Integer id) {
System.out.println("find user by id: " + id);
User user = new User();
user.setId(id);
user.setName("Name" + id);
return user;
}
7. 高阶使用
使用一:提供接口
使用二:提供父类
测试及调用
在需要缓存的地方,注入缓存对象即可,比如:
调用:
在缓存中根据 Key 获取对应 map 中的数据:
第一步:在使用缓存的地方注入缓存管理对象:
第二步:取值
8. 缓存策略
如果存满了,从存中移除数据的策略,常见的有 FIFO, LRU、LFU.
- FIFO (First in First Out)先进先出策略,即先放入缓存的数据先被移除
- LRU (Least Recently Used)最久未使用策略,即使用时间距离现在最久的那个数据被除
- LFU (Least Frequently Used)最少使用策略,即一定时间内使用次数(频率)最少的那个数据被移除
- TTL (Time To Live)存活期,即从缓存中创建时间点开始至到期的一个时间段(不管在这个时间段内有没被访问过都将过期 TT (Time To ldle)空闲期,即一个数据多久没有被访问就从缓存中移除的时间。
9. 存管理器
通过 @Enable Caching
注解自动化配置合适的存管理器(Cachemanager), Spring Boot 根据下面的顺序去侦测缓存提供者
- Generic
- Jcache (JSR-107)
- Ehcache 2. X
- Hazelcast
- Infnispan
- Redis
- Guava
- Simple
可以通过配置属性 spring.cache.type 来强制指定,即 spring.cache.type=redis