zoukankan      html  css  js  c++  java
  • 深入浅出 Spring Cache 使用与整合(附源码解析)

    深入浅出 Spring Cache 使用与整合(附源码解析)

    个人开发环境

    java环境:Jdk1.8.0_60

    编译器:IntelliJ IDEA 2019.1

    springCache官方文档:https://docs.spring.io/spring/docs/5.1.9.RELEASE/spring-framework-reference/integration.html#cache

    一、Spring缓存抽象

    SpringCache产生的背景其实与Spring产生的背景有点类似。由于Java EE 系统框架臃肿、低效,代码可观性低,对象创建和依赖关系复杂,Spring框架出来了,目前基本上所有的Java后台项目都离不开Spring或SpringBoot(对Spring的进一步封装简化)。现在项目面临高并发的问题越来越多,各类缓存的应用也增多,那么在通用的Spring框架上,就需要有一种更加便捷简单的方式,来完成缓存的支持。
    SpringCache本身是一个缓存体系的抽象实现,并没有具体的缓存能力,要使用SpringCache还需要配合具体的缓存实现来完成。

    • Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
    • Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache ,ConcurrentMapCache等;

    二、重要注解、参数

    名称 解释
    Cache 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等
    CacheManager 缓存管理器,管理各种缓存(cache)组件
    @Cacheable 主要针对方法配置,能够根据方法的请求参数对其进行缓存
    @CacheEvict 清空缓存
    @CachePut 保证方法被调用,又希望结果被缓存。 与@Cacheable区别在于是否每次都调用方法,常用于更新
    @EnableCaching 开启基于注解的缓存
    keyGenerator 缓存数据时key生成策略
    serialize 缓存数据时value序列化策略
    @CacheConfig 统一配置本类的缓存注解的属性

    @Cacheable/@CachePut/@CacheEvict 主要的参数

    名称 解释
    value 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}
    key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写, 如果不指定,则缺省按照方法的所有参数进行组合 例如: @Cacheable(value=”testcache”,key=”#id”)
    condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false, 只有为 true 才进行缓存/清除缓存 例如:@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
    unless 否定缓存。当条件结果为TRUE时,就不会缓存。 @Cacheable(value=”testcache”,unless=”#userName.length()>2”)
    allEntries (@CacheEvict ) 是否清空所有缓存内容,缺省为 false,如果指定为 true, 则方法调用后将立即清空所有缓存 例如: @CachEvict(value=”testcache”,allEntries=true)
    beforeInvocation (@CacheEvict) 是否在方法执行前就清空,缺省为 false,如果指定为 true, 则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法 执行抛出异常,则不会清空缓存 例如: @CachEvict(value=”testcache”,beforeInvocation=true)

    三、SpEL上下文数据

    Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:

    名称 位置 描述 示例
    methodName root对象 当前被调用的方法名 #root.methodname
    method root对象 当前被调用的方法 #root.method.name
    target root对象 当前被调用的目标对象实例 #root.target
    targetClass root对象 当前被调用的目标对象的类 #root.targetClass
    args root对象 当前被调用的方法的参数列表 #root.args[0]
    caches root对象 当前方法调用使用的缓存列表 #root.caches[0].name
    Argument Name 执行上下文 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 #artsian.id
    result 执行上下文 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) #result

    四、实战

    1.导入依赖

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

    2.启动类开启缓存注解@EnableCaching

    /**
     * 1、开启基于注解的缓存 @EnableCaching
     * 2、标注缓存注解即可
     * 	  @Cacheable
     * 	  @CacheEvict
     * 	  @CachePut
     */
    @SpringBootApplication
    @EnableCaching  //开启缓存
    public class CacheApplication{
        public static void main(String[] args) {
            SpringApplication.run(CacheApplication.class, args);
        }
    
    }
    

    3.缓存 @Cacheable

    运行流程:

    @Cacheable方法运行之前,先去查询Cache(缓存组件),按照cacheNames/value指定的名字获取,第一次获取缓存如果没有Cache组件会自动创建。

    源码分析

    public @interface Cacheable {
        // cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存
        @AliasFor("cacheNames")
        String[] value() default {};
        @AliasFor("value")
        String[] cacheNames() default {};
    
        // 缓存数据使用的key;可以用它来指定。默认是使用方法参数的值
        String key() default "";
    
        // key的生成器;可以自己指定key的生成器的组件id,key/keyGenerator:二选一使用;
        String keyGenerator() default "";
    
        // 指定缓存管理器;或者cacheResolver指定获取解析器 作用得到缓存集合
        String cacheManager() default "";
        String cacheResolver() default "";
    
        // 条件符合则缓存,编写SpEL:condition = "#a0>1":第一个参数的值大于1的时候才进行缓存
        String condition() default "";
        
        // 条件符合则不缓存,编写SpEL:unless = "#a0==2":如果第一个参数的值是2,结果不缓存;
        String unless() default "";
    
        // 是否使用异步模式,是否启用异步模式
        boolean sync() default false;
    }
    

    操练一下

    @Cacheable(value = "emp" ,key = "targetClass + methodName +#p0")
    public Employee getEmp(Integer id){
        System.out.println("查询"+id+"号员工");
        Employee emp = employeeMapper.getEmpById(id);
        return emp;
    }
    

    踩坑

    1、属性value/cacheNames是必需的,它指定了你的缓存存放在哪块命名空间。

    2、属性key是使用的spEL表达式

    注意:踩坑,如果你把methodName换成method运行会报错,观察它们的返回类型,原因在于methodNameStringmethohMethod

    Employee实体类一定要实现序列化public class Employee implements Serializable,否则会报java.io.NotSerializableException异常。

    4.更新 @CachePut

    @CachePut既调用方法,又更新缓存数据;同步更新缓存。简单来说就是用户修改数据同步更新缓存数据。

    源码分析:(皮一下,去事故与@Cacheable相同,不做过多解释了,狗头护体~~~)

    public @interface CachePut {
        
        @AliasFor("cacheNames")
        String[] value() default {};
        
        @AliasFor("value")
        String[] cacheNames() default {};
    
        String key() default "";
    
        String keyGenerator() default "";
    
        String cacheManager() default "";
    
        String cacheResolver() default "";
    
        String condition() default "";
    
        String unless() default "";
    }
    

    操练一下

    注意该注解的value/cahceNameskey 必须与要更新的缓存相同,也就是与@Cacheable 相同。

    @CachePut(value = "emp" ,key = "#employee.id")
    public Employee updateEmp(Employee employee){
        System.out.println("updateEmp:"+employee);
        employeeMapper.updateEmp(employee);
        return employee;
    }
    

    5.清除 @CacheEvict

    @CachEvict 的作用 主要针对方法配置,能够根据一定的条件对缓存进行清空 。

    源码分析

    public @interface CacheEvict {
        // 同上同上同上
        @AliasFor("cacheNames")
        String[] value() default {};
        @AliasFor("value")
        String[] cacheNames() default {};
        String key() default "";
        String keyGenerator() default "";
        String cacheManager() default "";
        String cacheResolver() default "";
        String condition() default "";
    
        // 指定清除这个缓存中所有的数据,示例:@CachEvict(value=”emp”,allEntries=true)
        boolean allEntries() default false;
    
        /*
         * 示例: @CachEvict(value=”emp”,beforeInvocation=true)
         * 代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
         *
         * 示例: @CachEvict(value=”emp”,beforeInvocation=false)(默认)
         * 缓存的清除是否在方法之前执行 , 默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除
         */
        boolean beforeInvocation() default false;
    }
    

    操练一下

    //方法调用后清空所有缓存
    @CacheEvict(value="accountCache", allEntries=true)
    public void deleteEmp() {
    	employeeMapper.deleteAll();
    }
    

    6.组合 @Caching

    有时候我们可能组合多个Cache注解使用,此时就需要@Caching组合多个注解标签了。

    源码分析

    public @interface Caching {
        // 用于指定多个缓存设置操作
        Cacheable[] cacheable() default {};
        
        // 用于指定多个缓存更新操作
        CachePut[] put() default {};
        
        // 用于指定多个缓存失效操作
        CacheEvict[] evict() default {};
    }
    
    

    操练一下

    // @Caching 定义复杂的缓存规则
    @Caching(
        cacheable = {
            @Cacheable(value="emp",key = "#lastName")
        },
        put = {
            @CachePut(value="emp",key = "#result.id"),
            @CachePut(value="emp",key = "#result.email")
        }
    )
    public Employee getEmpByLastName(String lastName){
        return employeeMapper.getEmpByLastName(lastName);
    }
    
    

    7.全局配置 @CacheConfig

    当我们需要缓存的地方越来越多,可以使用@CacheConfig(cacheNames = {"emp"})注解来统一指定value的值,这时可省略value,如果你在你的方法依旧写上了value,那么依然以方法的value值为准。

    源码分析

    public @interface Caching {
        // 用于指定多个缓存设置操作
        Cacheable[] cacheable() default {};
        
        // 用于指定多个缓存更新操作
        CachePut[] put() default {};
        
        // 用于指定多个缓存失效操作
        CacheEvict[] evict() default {};
    }
    
    

    操练一下

    @CacheConfig(cacheNames = {"emp"}, /*keyGenerator = "cacheKeyGenerator"*/)
    public class EmployeeServiceImpl implements EmployeeService {
        @Override
        @Cacheable(/*value = ‘emp’*/ key = "targetClass + methodName +#p0")
        public Employee getEmp(Integer id){
            Employee emp = employeeMapper.getEmpById(id);
            return emp;
        }
    }
    
    

    8.主键生成策略 keyGenerator

    操练一下

    创建CacheConfig配置类

    @Configuration
    public class CacheConfig {
    
    	/**
    	 * 生成缓存主键策略 (方法名+参数)
    	 * 
    	 * @return KeyGenerator
    	 */
    	@Bean("cacheKeyGenerator")
    	public KeyGenerator keyGenerator() {
    		return (target, method, params) -> (method.getName() + " [ " + Arrays.asList(params) + " ]");
    	}
    }
    
    
    

    可以在@CacheConfig指定生成策略,也可以在@Cacheable/@CachePut/@CacheEvict指定key生成策略

    @CacheConfig(cacheNames = {"emp"}, keyGenerator = "cacheKeyGenerator")
    public class EmployeeServiceImpl implements EmployeeService {
        @Override
        @Cacheable(/*value = ‘emp’,keyGenerator = "cacheKeyGenerator"*/)
        public Employee getEmp(Integer id){
            Employee emp = employeeMapper.getEmpById(id);
            return emp;
        }
    }
    
    
  • 相关阅读:
    MySQL Explain优化
    mysql联合索引
    MySQL索引类型
    php常用的排序算法与二分法查找
    mysql 主从复制(mysql双机热备的实现)
    mysql 主从复制 (2)
    Linux 查看进程之PS命令
    PHP isset 和 array_key_exists 对比
    Linux md5sum 的用法
    Linux Sphinx 安装与使用
  • 原文地址:https://www.cnblogs.com/yoci/p/11595226.html
Copyright © 2011-2022 走看看