SpringBoot系列(十五)整合缓存,项目会用得到的技术

一、缓存有什么用?

 缓存应该是我们每一个系统都应该考虑使用的,这样可以加速系统的访问,提升系统的性能。比如我们经常需要访问的高频数据,将此类数据放在缓存中,可以大幅度提升系统的响应速度。原因就是一般来说我们的数据都是存在数据库中,但是高频的访问数据库不仅会对数据库造成压力,一定程度上还会影响响应速度,影响用户体验。如果引入了缓存,不仅能提升访问性能的同时降低数据库的压力。

二、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 表达式(何时beforeInvocationfalse)中可用。对于支持的包装器(例如 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都可以自己配置,具体的下一篇文章会介绍。

posted @ 2021-07-06 09:35  全栈学习笔记  阅读(1388)  评论(0编辑  收藏  举报