深入浅出 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
运行会报错,观察它们的返回类型,原因在于methodName
是String
而methoh
是Method
。
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/cahceNames
和 key
必须与要更新的缓存相同,也就是与@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;
}
}