SpringBoot cache source code analysis

SpringBoot Cache cache

Hello everyone, I am thinking about it.

Recently, I am reviewing SpringBoot related source code. Found a lot of previous problems that could have a more elegant solution.

Just like Cache cache, don't many people think it. Integrating Mybatis, only the second-level cache of Mybatis can operate? Or just access a cache database like Redis.

In fact, SpringBoot's SimpleCache is also a memory-based caching technology, suitable for caching a small amount of data

  1. Buffer frequently queried data
  2. Buffering data that is computationally expensive
  3. Record the number of user visits to limit the flow of the interface flow control
  4. Record form submission status to prevent duplicate submissions.

Before I knew enough about SpringBoot Cache, I might encapsulate a Map and cache it myself.

But I found that SpringBoot Cache supports so many caches

SpringBoot-cache loading order

SpringBoot-cache loading order

When we do not introduce dependencies such as JCache and Redis, SpringBoot uses SimpleCache as a cache by default, and its underlying layer uses ConcurrentMap as a container to store cached content, and has a complete API

SimpleCache property ConcurrentMap

SimpleCache property ConcurrentMap

So yeah~

Taoism is not enough, you have to learn~

enter text

First simply use SpringBoot to initialize and create an SSM project to implement a table CRUD. This is not the point.

Add annotations to the SpringBoot startup class  @EnableCaching and enable caching

You can find the SpringBoot automatic configuration class by intercepting the breakpoint  org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration (this step is related to the Spring automatic configuration class. If you don’t know how to find it, you can read the "Spring Automatic Configuration Class" related information or find me)

SpringBoot Cache loading order

SpringBoot Cache loading order

We found  CacheAutoConfiguration that introducing CacheConfigurationImportSelector.class

CacheAutoConfiguration.java

CacheAutoConfiguration.java

CacheConfigurationImportSelector 是 CacheAutoConfiguration 中第一个内部类,它实现了 ImportSelector 重写了 selectImports 方法,那就是要重新的自动配置一下

我们就可以找到文章开头的那张图,在不引入其他 cache 的情况下,默认采用 SimpleCache 的放松

springboot-cache loading order

springboot-cache 加载顺序

SimpleCacheConfiguration 里面只有一个 @Bean 注解,就是返回一个 ConcurrentMapCacheManager 而 ConcurrentMapCacheManager 里面就有我们之前看到的

private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);

还有一个最重要的方法, Cache getCache(String name) ,值得一提的是 ConcurrentMapCacheManager 并不是 SpringBoot 中的源码,而是 Spring-context 中的。

Cacheable running process

Cacheable 运行流程

简单看一下,就是先到 cacheMap (ConcurrentMap) 中获取一下。没获取到就进入业务逻辑进行查询

object code

目标代码

文字描述一下运行流程

  1. 方法运行之前,先去查询 Cache 缓存组件,安装 CacheName 指定的名字获取,第一次获取缓存没有 Cache 组件时,就去创建 createConcurrentMapCache(name)
  2. 去 Cache 中查找缓存内容,使用 key 默认就是方法参数,如果是多个参数,总是会根据一种策略生成一种 key
  3. 没有查到缓存就调用目标方法
  4. 然后将目标方法结果放入缓存中

一句话总结: @Cacheable 标注的方法执行前会查询换成中是否有这个数据,默认按照参数作为key进行查询,如果没有调用目标方法将结果放入缓存,以后在调用时,直接使用缓存中数据。

除了 @Cacheable 注解之外呢,还有其他两种常用注解

@CachePut(Update)

即调用方法,又更新缓存,一般用于更新操作。

运行流程:

  1. 先调用目标方法
  2. 将目标方法结果缓存起来

CachePut case

CachePut 案例

@CacheEvict(Delete)

清除缓存,需要指定名字和key

属性:

  • value / cacheNames 缓存名字
  • key 缓存键
  • allEntrles 是否清除这个缓存 value 中的所有 key,如果为 true 则清除所有 key(与 key 属性二选一)
  • beforelnvocation 默认 false,执行方法后再清除缓存,如果设置为 true 则执行目标方法前清除缓存 作用:可能目标方法存在异常 实际数据清除了,但因为别的事务导致异常,方法执行失败,缓存就没有删除,(数据库已删除,缓存未删除)

CacheEvict case

CacheEvict 案例

基于 Redis 的缓存

第一步启动一个 Redis 然后项目中添加 redis 依赖

Start Redis

启动 Redis

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

通过断点老办法,我们可以找到 RedisAutoConfiguration

RedisAutoConfiguration

RedisAutoConfiguration

熟悉的感觉来了,一个 redisTemplate 还有一个 stringRedisTemplate 其实 stringRedisTemplate 就是继承了 redisTemplate

SimpleRedisTemplate inherits RedisTemplate

SimpleRedisTemplate 继承 RedisTemplate

简单的介绍了一下 SpringBoot 自动配置 Redis 相关的源码之后,迅速的写一个例子吧

缓存顺利的加载到 Redis 中了

没有 Redis 之前使用的 SimpleCache ,使用了 仅仅是因为加入了 Reids 的依赖,缓存就顺利的写到了 Redis 里,这是因为 SpringBoot 自动配置顺序有关:

我们可以看到 RedisCacheConfiguration 高于 SimpleCacheConfiguration 的

难道是 SpringBoot 一个一个扫描,发现一个之后就 continue 吗?当然不是了,SpringBoot 就玩的很优雅

我们打开原先的 SimpleCacheConfiguration

可以看到注解上有这样一段代码

@ConditionalOnMissingBean(CacheManager.class)

@Conditional 的意思是如果存在或者 (类、注解...) 的情况下,就加载此配置类,这样是 SpringBoot 自动化配置的一个核心点!

那 @ConditionalOnMissingBean 的意思是,如果存在这个类,就不让这个自动配置类生效。我们再根据上图的加载优先顺序,每个配置类中,都有这样一句话。也都有一个 cacheManager 的 Bean

 @Bean
 ConcurrentMapCacheManager cacheManager()...

This means that whoever creates  CacheManager the Cache configuration class that is subsequently loaded cannot be instantiated.

The design is very ingenious, isn't it? This idea is reflected in other places in SpringBoot!

Now that we already know how SpringBoot creates CacheManager by autoloading, of course we can also customize CacheManger in the same way

The default configuration of Redis is to use the serialization that comes with JDK to cache the value value, we can modify this configuration in the same way

 @Bean
 public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
  // 分别创建String和JSON格式序列化对象,对缓存数据key和value进行转换
  RedisSerializer<String> strSerializer = new StringRedisSerializer();
  Jackson2JsonRedisSerializer jacksonSeial =
    new Jackson2JsonRedisSerializer(Object.class);
  // 解决查询缓存转换异常的问题
  ObjectMapper om = new ObjectMapper();
  om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  jacksonSeial.setObjectMapper(om);
  // 定制缓存数据序列化方式及时效
  RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
    // 设置有效期为1天
    .entryTtl(Duration.ofDays(1))
    // 设置 key 的序列化方式为 String
    .serializeKeysWith(RedisSerializationContext.SerializationPair
      .fromSerializer(strSerializer))
    // 设置 value 的序列化方式为 Json
    .serializeValuesWith(RedisSerializationContext.SerializationPair
      .fromSerializer(jacksonSeial)) .disableCachingNullValues();
  RedisCacheManager cacheManager = RedisCacheManager .builder(redisConnectionFactory).cacheDefaults(config).build();
  return cacheManager;
 }

Take another look at the effect!

Thanks for reading!

Guess you like

Origin juejin.im/post/7233996255101763645