深入浅出 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=
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;
    }
}

posted @ 2019-09-26 23:49  Y.yang  阅读(2810)  评论(1编辑  收藏  举报