SpringBoot - SpringCache缓存

一、概述

SpringCache本身是一个缓存体系的抽象实现,并没有具体的缓存能力,要使用SpringCache还需要配合具体的缓存实现来完成。
虽然如此,但是SpringCache是所有Spring支持的缓存结构的基础,而且所有的缓存的使用最后都要归结于SpringCache。
它可以将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法;

二、缓存注解

SpringCache缓存功能的实现是依靠下面的这几个注解完成的。

  • @EnableCaching:开启缓存功能
  • @Cacheable:定义缓存,用于触发缓存
  • @CachePut:定义更新缓存,触发缓存更新
  • @CacheEvict:定义清除缓存,触发缓存清除
  • @Caching:组合定义多种缓存功能
  • @CacheConfig:定义公共设置,位于class之上

1、@CacheConfig

该注解标注于之上,用于配置该类中会用到的一些公共的缓存相关配置。
@CacheConfig(cacheNames = “pers.liuchengyin.service.blogServiceImpl”)
cachNames的值pers.liuchengyin.service.blogServiceImpl就是这个类公共的缓存对象,就相当于一个名为pers.liuchengyin.service.blogServiceImplMap对象,下文简称对象

2、@Cacheable

用在查询的方法上,方法的返回值将被加入缓存。该注解标注的方法每次被调用前都会触发缓存校验,校验指定参数的缓存是否已存在(已发生过相同参数的调用),若存在,直接返回缓存结果,否则执行方法内容,最后将方法执行结果保存到缓存中。

该注解有这些参数:valuecacheNameskeyconditionunlesskeyGeneratorcacheManagercacheResolver

valuecacheNames是两个同等的参数。cacheNames是Spring4新增的,作为value的别名,用于指定缓存对象,不是必须指定的。如果不指定则使用上面配置的CacheConfig配置的cacheNames

key就是缓存对象存储在Map集合中的key值,不是必需,缺省按照方法的所有参数组合作 为key值, 可以自己配置,但需要SpEL表达式。比如

@Cacheable(key = "#p0")    // 表示第一个参数作为缓存的key值
getBlog(Integer id, String name){}
@Cacheable(key = "#id")    // 也可以用参数名作为缓存的key值,这里的参数是id
getBlog(Integer id, String name){}
@Cacheable(key = "#user.id")   // 甚至可以用对象里的某个属性作为key值
getBlog(Integer id, User user){}

condition缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存。比如

@Cacheable(key = "#p0",condition = "#username.lenth < 3")
getBlog(Integer id, String username){}		// 只有第二个参数长度小于3的时候才缓存进去

unlesscondition相似,但unless是方法被调用之后才做判断的,所以可以对结果result进行判断是否去缓存。

keyGenerator用于指定key 生成器,非必需。
若需要指定一个自定义的key生成器,我们需要去实现 org.springframework.cache.interceptor.KeyGenerator接口,并使用该参数来指定

cacheManager用于指定使用哪个缓存管理器,非必需。只有当有多个缓存管理器时才需要使用。

cacheResolver用于指定使用那个缓存解析器,非必需。需通过 org.springframework.cache.interceptor.CacheResolver接口来实现自己的缓存解析器,并用该参数指定。

3、@CachePut

用于更新缓存,无论结果是否已经缓存,都会在方法执行结束插入缓存,相当于更新缓存,一般用于更新方法新增方法之上。其参数与@Cacheable类似。

4、@CacheEvict

配置于方法上,通常用在删除方法上,用来从缓存中移除相应数据。 除了同@Cacheable一样的参数之外,它还有下面两个参数:
allEntries,非必需,默认为false。当为true时,会移除对应缓存对象的所有缓存数据,也就是移除cacheNamevalue指定的对象的缓存数据。
beforeInvocation,非必需, 默认为false,会在调用方法之后移除数据。 当为 true时,会在调用方法之前移除数据。

5、@Caching

配置于方法上,用于定义复杂的缓存规则,比如

@Caching(
	cacheable={
		@Cacheable(key = "#name")
	},
	put={
		@CachePut("#result.id"),
		@CachePut("#result.email")
	}
)
Blog getByBlogNmae(String name){}

6、小结

一般来说,我们都在Service实现层的类上面使用@CacheConfig来指定公共缓存配置,在查询的方法上使用@Cacheable,在新增/修改的方法上使用@CachePut,在删除的方法上使用@CacheEvict

三、原理

SpringBoot中的自动配置类CacheAutoConfiguration中的缓存配置类有如下几种:

org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration
org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration

其中,SimpleCacheConfiguration是默认生效的。它的作用就是给容器中注册一个CacheManager:ConcurrentMapCacheManager。这个Manager可以获取和创建ConcurrentMapCache类型的组件,可以将数据保存到ConcurrenMap中。
CacheManager缓存管理器,它是来管理多个Cache组件的,对于缓存的CRUD操作实际上是在Cache组件中完成的。前面所说的cacheName/value就是每一个Cache组件的名字。key也就是缓存的数据,也是就是说Cache组件里是Key-value键值对。这些键值对是以数组的形式存储在它们所对应的Cache组件里的。这个Key和前面所说的keyGenerator只需选择一个就行,一个是我们指定key,一个是根据生成器自动生成。

四、运行流程

①@Cacheable

1、方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取。
CacheManager先获取相应的缓存,第一次获取缓存如果没有Cache组件会自动创建。
2、去Cache中查找缓存的内容
3、没有查到缓存就调用目标方法
4、将目标方法返回的结果返回并放进缓存中
5、下一次来调用,因为有这个缓存值,就不会再调用目标方法,直接返回缓存中的值

②@CachePut

1、先调用目标方法 - 这里面就可能涉及数据库的数据修改
2、将目标方法的结果缓存起来(也就是覆盖老的缓存)
该注解所指定的Cache组件需要与对应@Cacheable的一致,不然就起不到修改的作用。

五、使用示例

1、引入依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
	<groupId>net.sf.ehcache</groupId>
	<artifactId>ehcache</artifactId>
</dependency>

2、配置ehcache.xml

ehcache.xml是缓存的配置文件,默认在resources下,可以通过yml配置文件修改其读取位置。

spring:
  cache:
    ehcache:
      config:claspath:config/ehcache_config.xml  # config文件夹下的echache_config.xml

这里我们就使用默认的

<ehcache>
    <diskStore path="java.io.tmpdir/cache"/>
    <!-- 默认缓存策略 -->
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
    />

    <!--
        自定义缓存策略
        name:缓存名称
        maxElementsInMemory:缓存最大个数,0表示无穷大
        eternal:缓存对象是否永久有效,如果为true,忽略timeToIdleSeconds、timeToLiveSeconds
        timeToIdleSeconds:允许对象处于空闲状态的最长时间,以秒为单位
        timeToLiveSeconds:对象允许存在于缓存中的最长时间,以秒为单位
        overflowToDisk:true表示当内存缓存的对象数目达到了maxElementsInMemory界限后,会把溢出的对象写到硬盘缓存中,对象需实现序列化接口
        diskPersistent:是否缓存虚拟机重启期数据,是否持久化磁盘缓存
        diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,以秒为单位
    -->
    <cache name="student_cache"
           maxElementsInMemory="10000"
           eternal="true"
           timeToIdleSeconds="120"
           timeToLiveSeconds="120"
           overflowToDisk="true"
           diskPersistent="true"
           diskExpiryThreadIntervalSeconds="600"
    />
</ehcache>

3、创建POJO类

@Data
public class Student implements Serializable {
    /** ID */
    private Long id;
    /** 姓名 */
    private String name;
    /** 性别 */
    private String sex;
    /** 班级 */
    protected String classGrade;
    /** 入学日期 */
    private Date admissionDate;
}

4、创建Service类

@Service
@CacheConfig(cacheNames = "student_cache")   // 缓存名
public class StudentServiceImpl implements StudentService {

    @Override
    @Cacheable  // 默认以参数组合为Key
    public Student getStudentById(Long id) {
        System.out.println("直接获取学生信息:");
        Student student = new Student();
        student.setId(1L);
        student.setName("柳成荫");
        student.setSex("男");
        student.setClassGrade("21班");
        student.setAdmissionDate(new Date());
        return student;
    }

    @Override
    @CachePut(key = "#student.id")   // 指定对象的Id为Key
    public Student updateStudent(Student student) {
        System.out.println("修改学生信息:");
        student.setName("九月清晨");
        return student;
    }

    @Override
    @CacheEvict(key = "#id")        // 删除Id为Key的缓存
    public void deleteStudentById(Long id) {
        System.out.println("删除学生信息:");
    }
    
}

5、测试

为了方便测试,直接使用SpringBootTest来进行测试,直接调用Service。

@RunWith(SpringRunner.class)
@SpringBootTest
public class DoTest {

    @Autowired
    private StudentService studentService;

    @Test
    public void test1(){
        // 调用查询信息
        Student student = studentService.getStudentById(1L);
        System.out.println(student);
        System.out.println("--------------------分割线-----------------------");
        // 调用查询信息
        Student student1 = studentService.getStudentById(1L);
        System.out.println(student1);
        System.out.println("--------------------分割线-----------------------");
        // 修改学生信息
        Student student2 = studentService.updateStudent(student1);
        System.out.println(student2);
        System.out.println("--------------------分割线-----------------------");
        // 调用查询信息
        Student student3 = studentService.getStudentById(1L);
        System.out.println(student3);
        System.out.println("--------------------分割线-----------------------");
        // 删除学生信息
        studentService.deleteStudentById(1L);
        System.out.println("--------------------分割线-----------------------");
        // 调用查询信息
        Student student4 = studentService.getStudentById(1L);
        System.out.println(student4);
    }

}

6、运行结果分析

运行结果
从运行截图和Service里的方法来看:
1、第一次查询时,发现是调用了方法的
2、第二次查询时,没有出现直接获取学生信息这一句,说明是从缓存中取的结果
3、接着,调用修改学生信息的方法,name被修改成九月清晨
4、第三次查询时,发现name变成了九月清晨,依旧是从缓存中获取的,说明修改了缓存
5、接着,调用删除学生信息的方法
6、第四次查询时,出现直接获取学生信息这一句,并且name变为柳成荫,说明删除缓存了,直接调用方法获取的结果

六、自定义缓存Key

Spring提供了一个root对象生成Key,如下图:
root
除此之外,我们也可也自定义缓存Key的生成器KeyGenerator,如下

@Component
public class MyKeyGenerator implements KeyGenerator {
    /**
     * 自定义生成Key
     * @param target 当前对象
     * @param method 当前请求的方法
     * @param params 方法的参数
     * @return
     */
    @Override
    public Object generate(Object target, Method method, Object... params) {
        String className = target.getClass().getSimpleName();
        String methodName = method.getName();
        String args = "";
        for (Object param : params) {
            if (param instanceof Long){
                args += param;
            }else if(param instanceof Student){
                Student student = (Student) param;
                args += student.getName();
            }
        }
        // 这只是个示例,根据自己需求进行返回Key
        return className + methodName + args;
    }
}

在使用的时候,我们只需要指定生成策略即可:

@Autowired
private MyKeyGenerator myKeyGenerator;

@Cacheable(keyGenerator = "myKeyGenerator")
public void getStudent(Long id, Student student){
	System.out.println("....");
}
发布了100 篇原创文章 · 获赞 25 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_40885085/article/details/104944011