一、缓存有什么用?
缓存应该是我们每一个系统都应该考虑使用的,这样可以加速系统的访问,提升系统的性能。比如我们经常需要访问的高频数据,将此类数据放在缓存中,可以大幅度提升系统的响应速度。原因就是一般来说我们的数据都是存在数据库中,但是高频的访问数据库不仅会对数据库造成压力,一定程度上还会影响响应速度,影响用户体验。如果引入了缓存,不仅能提升访问性能的同时降低数据库的压力。
二、JSR107规范
JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。
规范:
JCache规范定义了一种对Java对象临时在内存中进行缓存的方法,包括对象的创建、共享访问、假脱机(spooling)、失效、各JVM的一致性等,可被用于缓存JSP内最经常读取的数据,如产品目录和价格列表。利用JCACHE,多数查询的反应时间会因为有缓存的数据而加快(内部测试表明反应时间大约快15倍)。
Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry。其中CacheProvider就是缓存提供器,其中包含了多个缓存管理器(CacheManager),缓存管理器之下才是我们需要用到的缓存(Cache),缓存的形式就是键值对,即entry对象(Entry),每一个entry对象会对应一个Expiry,即可以对每一条缓存数据设置有效期。
上面都是抄过来的废话,看下面,实际操作一下。
三、Spring缓存抽象
Spring 3.1开始定义了org.springframework.cache.Cache 和org.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用JCache(JSR-107)注解简化我们开发。SpringBoot对缓存整合提供了很好的支持,支持不同的缓存规范(所谓的规范就是相当于面向接口编程,不同的缓存有不同的实现)。
其中Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache等;下面我们来看看,SpringBoot中默认的一些缓存配置与实现。默认的是SimpleCache。
四、SpringBoot中的缓存-注解开发
新建SpringBoot web项目,加入依赖配置:SpringBoot版本为2.2.6.RELEASE 库存文章了,版本是去年的。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
然后我们逐个介绍一下缓存开发中用到的一些注解。
- @EnableCaching
- @Cacheable
- @CachePut
- @CacheEvict
- @Caching
- @CacheConfig
1.
@EnableCaching
这个注解应该是最先了解的,把它放在启动类上面,表示开启基于注解的缓存,然后你用那些缓存相关的注解就能被Spring容器扫描到。这个注解不能缺,不然项目会报错。
@EnableCaching
@SpringBootApplication
public class DemoCacheApplication {
public static void main(String[] args) {
SpringApplication.run(DemoCacheApplication.class, args);
}
}
2.
@Cacheable
这个注解有很多的属性,可以定义一些缓存的规则。主要是用于方法之上,将方法的结果作为缓存的值,默认情况下将方法的参数作为缓存的键(key),也就是key-value的形式存储数据。首先介绍一下这个注解的运行机制:首先在方法运行之前,先根据注解配置的value或者是cacheNames到cacheManage中去查询缓存组件即Cache,当然如果第一次没有这个缓存就会自动创建。获取到缓存组件之后就用我们设置的key属性,或者是keyGenerator策略生成的key去查询键对应的缓存值,对应上面的entry对象。如果查到了值就返回给请求客户端,如果没有查到这个值就调用方法,然后将方法返回的结果缓存起来。
这里我们来说一说这个注解里面的一些属性的用法:
- value/cacheNames:这两个属性是一样的,都是指定一个缓存组件的名字,当然这个可以指定多个,是一个数组。
- key/keyGenerator:这两个有一定的区别,但是用的时候只需要用其中一个就行了。key是我们可以直接指定这个缓存结果对应的key是什么,到时候直接去Cache里面查就行了,keyGenerator就是可以自定义一个key的生成策略,然后生成key,两者从结果上来看都是生成了key,所以两者只需要用其中一个就OK了。
- condition:在符合这个条件下才进行缓存
- unless:当这个条件为假的时候才进行缓存,与condition相反
当然还有cacheManager(缓存管理器)以及cacheResolver(缓存解析器),这两个都可以自己定义。两个都差不多,二者用其中一个就OK。
下面我们来看看SpEl写法,因为我们的key或者是condition,或者是unless属性都会用到这个写法。
来自官方的spel 语法
Name | Location | Description | Example |
---|---|---|---|
methodName | Root object | 被调用方法的名称 | #root.methodName |
method | Root object | 被调用的方法 | #root.method.name |
target | Root object | 被调用的目标对象 | #root.target |
targetClass | Root object | 被调用目标的类 | #root.targetClass |
args | Root object | 用于调用目标的参数(作为数组) | #root.args[0] |
caches | Root object | 运行当前方法的缓存集合 | #root.caches[0].name |
Argument name | Evaluation context | 任何方法参数的名称。如果名称不可用(可能是由于没有调试信息),则参数名称也可在代表参数索引的#a<#arg> where下#arg (从 开始0 )。 | #iban or #a0 (you can also use #p0 or #p<#arg> notation as an alias). |
result | Evaluation context | 方法调用的结果(要缓存的值)。仅在unless 表达式、cache put 表达式(用于计算key )或cache evict 表达式(何时beforeInvocation 是false )中可用。对于支持的包装器(例如 Optional ),#result 指的是实际对象,而不是包装器。 | #result |
具体使用如下:
@GetMapping("/cacheAble")
@Cacheable(value = "cache",key = "#root.args[0]",condition = "#id>20")
public Student cacheAble(@RequestParam Integer id) {
Student student = (Student) hashMap.get(id);
System.out.println("第一次调用了方法,添加缓存");
return student;
}
上面就相当于是将第一个方法参数id作为缓存的key ,返回值Student 作为值。缓存的名称就是cache,设置在参数id大于20的时候才进行缓存,unless 则是相反的。
3.@CachePut
这个注解是用来更新缓存的,一般情况下都是用在更新数据的接口上,示例如下:
@PostMapping("/putCache")
@CachePut(value = "cache",key = "#result.id")
public Student putCache(@RequestBody Student student) {
hashMap.replace(student.getId(),student);
System.out.println("调用了方法,也更新了缓存");
return student;
}
4.@CacheEvict
删除缓存的注解,可以设置删除全部缓存还是删除部分缓存数据。如下
@CacheEvict(value = "cache",key = "#id",beforeInvocation = true,allEntries = false)
@GetMapping("/cacheEvict")
public void cacheEvict(@RequestParam Integer id) {
hashMap.remove(id);
System.out.println("缓存删除");
}
beforeInvocation
设置为true ,表示在方法执行之前进行缓存删除,默认为false,``allEntries`设置为true ,表示删除这个缓存名称下面的所有缓存。
5.@Caching
借助官方的话说,有时,需要指定多个相同类型的注解(例如@CacheEvict
或 @CachePut
)——例如,因为不同缓存之间的条件或键表达式不同。@Caching
允许在同一方法上使用多个嵌套的 @Cacheable
、@CachePut
和@CacheEvict
注释。实际上就是,各个缓存注解可以配合使用,操作不同的缓存空间。
@Caching(
cacheable = {
@Cacheable(value = "cache",key = "#id"),
@Cacheable(value = "cache1",key = "#id")
},
evict = {@CacheEvict(value = "cache2",key = "#id")}
)
@GetMapping("/caching")
public Student caching(@RequestParam Integer id) {
Student student = (Student)hashMap.get(id);
System.out.println("新增缓存了,也删除了缓存,但是操作的cache不一样");
return student;
}
代码实现的就是将数据缓存到cache,cache1,再将cache2的缓存删除掉。
6.
@CacheConfig
这是一个类级别的缓存配置注解,像缓存名称,缓存处理器(cacheResolver),缓存管理器(cacheManager),这些配置可以直接配置到类上面,这样方法对应的就能少些一些重复代码。
//注解的四个属性
String[] cacheNames() default {};
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
示例: 在类上面,加上注解,配置好对应的缓存名称,key生成策略等,keyGenerator,cacheManager,cacheResolver这三个东西留着下篇文章将redis缓存的时候讲,主要是比较多。
@CacheConfig(cacheNames = "chache")
public class CacheController {}
确实,我们的缓存还能从cacheManager中获取,这很好理解,cacheManager是管理缓存的,当然也能获取缓存了。
@GetMapping("/get")
public Student getCache(@RequestParam Integer id){
Cache cache = cacheManager.getCache("cache");
Cache.ValueWrapper student = cache.get(id);
Object o = student.get();
return (Student) o;
}
由于SpringBoot的良好扩展性,CacheManager,CacheResolver,
KeyGenerator都可以自己配置,具体的下一篇文章会介绍。