SpringBoot 캐시 소스 코드 분석

SpringBoot 캐시 캐시

안녕하세요 여러분, 생각하고 있습니다.

최근에는 SpringBoot 관련 소스코드를 리뷰하고 있습니다. 보다 우아한 솔루션을 가질 수 있는 많은 이전 문제를 찾았습니다.

캐시 캐시와 마찬가지로 많은 사람들이 생각하지 않습니다. 마이바티스 통합, 마이바티스 2차 캐쉬만 동작? 또는 Redis와 같은 캐시 데이터베이스에 액세스하십시오.

실제로 SpringBoot의 SimpleCache도 메모리 기반 캐싱 기술이므로 적은 양의 데이터를 캐싱하는 데 적합합니다.

  1. 자주 쿼리되는 데이터 버퍼링
  2. 연산 비용이 많이 드는 데이터 버퍼링
  3. 인터페이스 흐름 제어의 흐름을 제한하기 위해 사용자 방문 횟수를 기록합니다.
  4. 중복 제출을 방지하기 위해 양식 제출 상태를 기록합니다.

SpringBoot Cache에 대해 충분히 알기 전에 지도를 캡슐화하고 직접 캐시할 수 있습니다.

하지만 SpringBoot Cache가 너무 많은 캐시를 지원한다는 것을 알았습니다.

SpringBoot 캐시 로딩 순서

SpringBoot 캐시 로딩 순서

JCache 및 Redis와 같은 종속성을 도입하지 않을 때 SpringBoot는 기본적으로 SimpleCache를 캐시로 사용하고 기본 계층은 ConcurrentMap을 컨테이너로 사용하여 캐시된 콘텐츠를 저장하고 완전한 API를 갖습니다.

SimpleCache 속성 ConcurrentMap

SimpleCache 속성 ConcurrentMap

그래~

도교로는 부족하니 배워야지~

텍스트 입력

먼저 SpringBoot를 사용하여 SSM 프로젝트를 초기화하고 생성하여 테이블 CRUD를 구현합니다. 이것은 요점이 아닙니다.

SpringBoot 시작 클래스에 주석 추가  @EnableCaching 및 캐싱 활성화

중단점을 가로채서 SpringBoot 자동 구성 클래스를 찾을 수 있습니다  org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration . )

SpringBoot 캐시 로딩 순서

SpringBoot 캐시 로딩 순서

우리는  CacheAutoConfiguration 소개 CacheConfigurationImportSelector.class

CacheAutoConfiguration.java

CacheAutoConfiguration.java

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

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

springboot-cache 로딩 순서

springboot-cache 加载顺序

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

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

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

캐시 가능한 실행 프로세스

Cacheable 运行流程

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

개체 코드

目标代码

文字描述一下运行流程

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

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

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

@CachePut(Update)

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

运行流程:

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

CachePut 사례

CachePut 案例

@CacheEvict(Delete)

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

属性:

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

CacheEvict 사례

CacheEvict 案例

基于 Redis 的缓存

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

레디스 시작하기

启动 Redis

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

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

Redis 자동 구성

RedisAutoConfiguration

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

SimpleRedisTemplate은 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()...

즉,  CacheManager 나중에 로드되는 Cache 구성 클래스를 만드는 사람은 인스턴스화할 수 없습니다.

디자인이 굉장히 독창적이지 않나요? 이 아이디어는 SpringBoot의 다른 위치에 반영됩니다!

이제 SpringBoot가 자동 로딩을 통해 CacheManager를 생성하는 방법을 이미 알고 있으므로 동일한 방식으로 CacheManger를 사용자 지정할 수도 있습니다.

Redis의 기본 구성은 JDK와 함께 제공되는 직렬화를 사용하여 값 값을 캐시하는 것입니다. 동일한 방식으로 이 구성을 수정할 수 있습니다.

 @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;
 }

효과를 한번 더 보세요!

읽어 주셔서 감사합니다!

추천

출처juejin.im/post/7233996255101763645