前言
缓存在我们的实际开发中起着至关重要的作用,今天先对SpringBoot中的缓存技术先做一个整体的概述,并用spring默认的缓存ConcurrentMapCache进行简单的说明。本博客的例子基于上篇博客进行测试
1、Spring缓存抽象
Spring 定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术;Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCache,ConcurrentMapCache等;
CacheManager管理了多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中,Cache是一个类似Map的数据结构并临时存储以Key为索引的值,如下图:
1.1 几个重要该概念和缓存注解
2、原理分析
2.1 ctrl+n搜索CacheAutoConfiguration进入到CacheAutoConfiguration类
然后找到类CacheConfigurationImportSelector,它会给我们的容器导入一些所需要的配置组件,我们通过断点调试,发现导入了如下的缓存配置
static class CacheConfigurationImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
CacheType[] types = CacheType.values();
String[] imports = new String[types.length];
for (int i = 0; i < types.length; i++) {
imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
}
return imports;
}
}
默认是SimpleCacheConfiguration 生效
2.2 ctrl+N找到类SimpleCacheConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {
@Bean
ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties,
CacheManagerCustomizers cacheManagerCustomizers) {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return cacheManagerCustomizers.customize(cacheManager);
}
}
我们从这个类中可以看到有一个@Bean 说明给容器中注册了一个ConcurrentMapCacheManager类型的CacheManager ,接下来进入到ConcurrentMapCacheManager类中发现他实现了CacheManager,我们找到方法getCache,
从下面代码的逻辑中,此cachemanager就是为了创建和获取ConcurrentMapCache
@Override
@Nullable
public Cache getCache(String name) {
Cache cache = this.cacheMap.get(name);
if (cache == null && this.dynamic) {
synchronized (this.cacheMap) {
cache = this.cacheMap.get(name);
if (cache == null) {
cache = createConcurrentMapCache(name);
this.cacheMap.put(name, cache);
}
}
}
return cache;
}
我们调到ConcurrentMapCache中,发现数据存储在ConcurrentMap中
protected ConcurrentMapCache(String name, ConcurrentMap<Object, Object> store,
boolean allowNullValues, @Nullable SerializationDelegate serialization) {
super(allowNullValues);
Assert.notNull(name, "Name must not be null");
Assert.notNull(store, "Store must not be null");
this.name = name;
this.store = store;
this.serialization = serialization;
}
3、整合缓存
3.1 maven坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
3.2 启动类加上注解@EnableCaching让缓存生效
@SpringBootApplication
@MapperScan("com.atguigu.testdata.mapper") // 将mapper扫描到容器中
@EnableCaching
public class DataApplication {
public static void main(String[] args) {
SpringApplication.run(DataApplication.class,args);
}
}
3.3测试
@Cacheable(cacheNames = {"emp"})
@GetMapping("/emp/{id}")
public Employee getEmp(@PathVariable("id") Integer id){
return this.employeeMapper.getEmpById(id);
}
第一次查询结果发现是从数据库中查询,第二次查询发现没有走数据库,而是从缓存中查询,所以缓存生效。
4、运行流程
4.1、方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;(CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。(进入到getCache方法中实现的逻辑)
4.2、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key;SimpleKeyGenerator生成key的默认策略:
如果没有参数;key=new SimpleKey();
如果有一个参数:key=参数的值
如果有多个参数:key=new SimpleKey(params);
此时我们传的id是2,那么key=2
4.3、没有查到缓存就调用目标方法(查询数据库)
@Cacheable(cacheNames = {"emp"})
@GetMapping("/emp/{id}/{gender}")
public Employee getEmp(@PathVariable("id") Integer id,@PathVariable("gender") Integer gender){
return this.employeeMapper.getEmpById(id);
}
4.4、将目标方法返回的结果,放进缓存中
4.5 第二次调用缓存的时候,按照指定的 参数值作为key,如果有值,直接获取结果
4.6 核心总结:
1)、使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
2)、key使用keyGenerator生成的,默认是SimpleKeyGenerator
5、@Cacheable @CachePut @CacheEvict的几个属性
value | 缓存的名称,在Spring配置文件种定义,必须指定至少一个 | 例如:@Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”} |
---|---|---|
key | 缓存的key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | 例如:@Cacheable(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回true 或者false,只有为true 才进行缓存/清除缓存,在调用方法之前之后都能判断 | 例如:@Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
allEntries(@CacheEvict ) | 是否清空所有缓存内容,缺省为 false,如果指定为true,则方法调用后将立即清空所有缓存 | 例如:@CachEvict(value=”testcache”,allEntries=true) |
beforeInvocation(@CacheEvict) | 是否在方法执行前就清空,缺省为 false,如果指定为true,则在方法还没有执行的时候就清缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 | 例如:@CachEvict(value=”testcache”beforeInvocation=true) |
unless(@CachePut)(@Cacheable) | 用于否决缓存的,不像condition,该表达式只在方法执行之后判断,此时可以拿到返回值result进行判断。条件为true不会缓存,fasle才缓存 | 例如:@Cacheable(value=”testcache”,unless=”#result == null”) |
5.1 cacheNames/value:
指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存;
5.2 key:
(1)缓存数据使用的key,默认是使用方法参数的值
(2)指定key:
编写SpEL,如下图所示
此时我想自己指定一种id:方法名[参数值]
方式1:key
@Cacheable(cacheNames = {"emp"},key = "#root.methodName+'['+#id+']'")
@GetMapping("/emp/{id}/{gender}")
public Employee getEmp(@PathVariable("id") Integer id,@PathVariable("gender") Integer gender){
return this.employeeMapper.getEmpById(id);
}
方式2: keyGenerator:key的生成器;可以自己指定key的生成器的组件id
@Configuration
public class MyCacheConfig {
/**
* 自己配置缓存的key
* @return
*/
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName()+"["+ Arrays.asList(params).toString()+"]";
}
};
}
}
@Cacheable(cacheNames = {"emp"},keyGenerator = "myKeyGenerator")
@GetMapping("/emp/{id}")
public Employee getEmp(@PathVariable("id") Integer id){
return this.employeeMapper.getEmpById(id);
}
5.3 condition 指定符合条件的情况下才缓存
condition = “#id>0” #参数名进行指定
condition = “#a0>1”:第一个参数的值>1的时候才进行缓存
@Cacheable(cacheNames = {"emp"},keyGenerator = "myKeyGenerator",condition = "#a0>1")
5.4 unless:否定缓存
unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断
unless = “#result == null”
unless = “#a0==2”:如果第一个参数的值是2,结果不缓存;
5.5 sync:是否使用异步模式
默认是false,如果启用异步,那么unless属性就不支持了
后记
由于篇幅过长,下一篇博客将介绍一些其他的注解的用法