【springBoot】springboot2.X 整合spring默认的缓存及其原理(上)

前言

缓存在我们的实际开发中起着至关重要的作用,今天先对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属性就不支持了

后记

由于篇幅过长,下一篇博客将介绍一些其他的注解的用法

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值