Spring在3.1版本,就提供了一条基于注解的缓存策略,实际使用起来还是很丝滑的,本文将针对几个常用的注解进行简单的介绍说明,有需要的小伙伴可以尝试一下
本文主要知识点:
- @Cacheable: 缓存存在,则使用缓存;不存在,则执行方法,并将结果塞入缓存
- @CacheEvit: 失效缓存
- @CachePut: 更新缓存
I. 项目环境
1. 项目依赖
本项目借助SpringBoot 2.2.1.RELEASE
+ maven 3.5.3
+ IDEA
+ redis5.0
进行开发
开一个web服务用于测试
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies> 复制代码
全程使用默认配置,redis本机,端口6379,无密码
II. 缓存注解介绍
1. @Cacheable
这个注解用于修饰方法or类,当我们访问它修饰的方法时,优先从缓存中获取,若缓存中存在,则直接获取缓存的值;缓存不存在时,执行方法,并将结果写入缓存
这个注解,有两个比较核心的设置
/** * 与 cacheNames 效果等价 */ @AliasFor("cacheNames") String[] value() default {}; @AliasFor("value") String[] cacheNames() default {}; /** * 缓存key */ String key() default ""; 复制代码
cacheNames可以理解为缓存key的前缀,可以为组件缓存的key变量;当key不设置时,使用方法参数来初始化,注意key为SpEL表达式,因此如果要写字符串时,用单引号括起来
一个简单的使用姿势
/** * 首先从缓存中查,查到之后,直接返回缓存数据;否则执行方法,并将结果缓存 * <p> * redisKey: cacheNames + key 组合而成 --> 支持SpEL * redisValue: 返回结果 * * @param name * @return */ @Cacheable(cacheNames = "say", key = "'p_'+ #name") public String sayHello(String name) { return "hello+" + name + "-->" + UUID.randomUUID().toString(); } 复制代码
如我们传参为 yihuihui, 那么缓存key为 say::p_yihuihui
除了上面三个配置值之外,查看@Cacheable
注解源码的童鞋可以看到还有
condition
设置,这个表示当它设置的条件达成时,才写入缓存
/** * 满足condition条件的才写入缓存 * * @param age * @return */ @Cacheable(cacheNames = "condition", key = "#age", condition = "#age % 2 == 0") public String setByCondition(int age) { return "condition:" + age + "-->" + UUID.randomUUID().toString(); } 复制代码
上面这个case中,age为偶数的时候,才走缓存;否则不写缓存
接下来是unless
参数,从名字上可以看出它表示不满足条件时才写入缓存
/** * unless, 不满足条件才写入缓存 * * @param age * @return */ @Cacheable(cacheNames = "unless", key = "#age", unless = "#age % 2 == 0") public String setUnless(int age) { return "unless:" + age + "-->" + UUID.randomUUID().toString(); } 复制代码
2. @CachePut
不管缓存有没有,都将方法的返回结果写入缓存;适用于缓存更新
/** * 不管缓存有没有,都写入缓存 * * @param age * @return */ @CachePut(cacheNames = "t4", key = "#age") public String cachePut(int age) { return "t4:" + age + "-->" + UUID.randomUUID().toString(); } 复制代码
3. @CacheEvict
这个就是我们理解的删除缓存
/** * 失效缓存 * * @param name * @return */ @CacheEvict(cacheNames = "say", key = "'p_'+ #name") public String evict(String name) { return "evict+" + name + "-->" + UUID.randomUUID().toString(); } 复制代码
4. @Caching
在实际的工作中,经常会遇到一个数据变动,更新多个缓存的场景,对于这个场景,可以通过@Caching
来实现
/** * caching实现组合,添加缓存,并失效其他的缓存 * * @param age * @return */ @Caching(cacheable = @Cacheable(cacheNames = "caching", key = "#age"), evict = @CacheEvict(cacheNames = "t4", key = "#age")) public String caching(int age) { return "caching: " + age + "-->" + UUID.randomUUID().toString(); } 复制代码
上面这个就是组合操作
- 从
caching::age
缓存取数据,不存在时执行方法并写入缓存; - 失效缓存
t4::age
5. 异常时,缓存会怎样?
上面的几个case,都是正常的场景,当方法抛出异常时,这个缓存表现会怎样?
/** * 用于测试异常时,是否会写入缓存 * * @param age * @return */ @Cacheable(cacheNames = "exception", key = "#age") @Cacheable(cacheNames = "say", key = "'p_yihuihui'") public int exception(int age) { return 10 / age; } 复制代码
根据实测结果,当age==0
时,上面两个缓存都不会成功
6. 测试用例
接下来验证下缓存注解与上面描述的是否一致
@RestController public class IndexRest { @Autowired private BasicDemo helloService; @GetMapping(path = {"", "/"}) public String hello(String name) { return helloService.sayHello(name); } } 复制代码
上面这个主要是验证@Cacheable
注解,若缓存不命中,每次返回的结果应该都不一样,然而实际访问时,会发现返回的都是相同的
curl http://localhost:8080/?name=yihuihui 复制代码
失效缓存
@GetMapping(path = "evict") public String evict(String name) { return helloService.evict(String.valueOf(name)); } 复制代码
失效缓存,需要和上面的case配合起来使用
curl http://localhost:8080/evict?name=yihuihui curl http://localhost:8080/?name=yihuihui 复制代码
剩下其他的相关测试类就比较好理解了,一并贴出对应的代码
@GetMapping(path = "condition") public String t1(int age) { return helloService.setByCondition(age); } @GetMapping(path = "unless") public String t2(int age) { return helloService.setUnless(age); } @GetMapping(path = "exception") public String exception(int age) { try { return String.valueOf(helloService.exception(age)); } catch (Exception e) { return e.getMessage(); } } @GetMapping(path = "cachePut") public String cachePut(int age) { return helloService.cachePut(age); } 复制代码
7. 小结
最后管理小结一下Spring提供的几个缓存注解
@Cacheable
: 缓存存在,则从缓存取;否则执行方法,并将返回结果写入缓存@CacheEvit
: 失效缓存@CachePut
: 更新缓存@Caching
: 都注解组合
上面虽说可以满足常见的缓存使用场景,但是有一个非常重要的点没有说明,缓存失效时间应该怎么设置???