Skip to content

Latest commit

 

History

History
264 lines (203 loc) · 9.99 KB

File metadata and controls

264 lines (203 loc) · 9.99 KB

Spring Cache 介绍

简单介绍

我们都知道有经验的程序员,如果配置缓存,当用到 Jedis 的时候会自己配置一些注解,并且利用 @Aspect。而 Spring 的 Cache 就帮我们做了一些这样的事情。

Spring 3.1 之后引入了基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(如 EHCache 或者 Redis),而是一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果。

Spring 的缓存技术还具备相当的灵活性,不仅能够使用 SpEL(Spring Expression Language)来定义缓存的 key 和各种 condition,还提供开箱即用的缓存临时存储方案,也支持和主流的专业缓存例如 Redis、EHCache 集成。

Spring 4.1 之后又改进了很多,Spring Cache 属于 Spring framework 的一部分,在如下这个包里面:

Spring Cache Context Jar

Spring Cache 里面的主要的类

Spring 定义了 org.springframework.cache.CacheManager 和 org.springframework.cache.Cache 接口用来统一不同的缓存的技术。我们打开 CachingConfigurer 也会发现主要的几个东西:

public interface CachingConfigurer {
   CacheManager cacheManager();
   CacheResolver cacheResolver();
   KeyGenerator keyGenerator();
   CacheErrorHandler errorHandler();
}

Cache 接口包含缓存的各种操作(增加、删除、获得缓存)。Spring 的 Framework 又做了好多,Cache 的默认的缓存的实现如下:

spring cache son classes

其中包括:Ehcache、GuavaCache 等很多流行的 Cache 的实现,而 RedisCache 的实现我们后面讲。我们打开 CacheManager 看一下:

public interface CacheManager {
   Cache getCache(String name);
   Collection<String> getCacheNames();
}

所以 CacheManager 是 Spring 提供的各种缓存技术抽象接口,通过它管理 Spring Framework 里面默认的实现的 CacheManager 有如下内容:

Spring Framework CacheManager

CacheResolver 解析器,用于根据实际情况来动态解析使用哪个 Cache,默认实现的有如下:

CacheResolver

KeyGenerator 当我们使用 Cache 注解的时候,默认 key 的生成规则:

KeyGenerator

Spring Cache 里面的主要的注解

@Cacheable 应用到读取数据的方法上,即可缓存的方法,如查找方法:先从缓存中读取,如果没有再调用方法获取数据,然后把数据添加到缓存中。

public @interface Cacheable {
   @AliasFor("cacheNames")
   String[] value() default {};
//cache的名字。可以根据名字设置不同cache处理类。redis里面可以根据cache名字设置不同的失效时间。
   @AliasFor("value")
   String[] cacheNames() default {};
//缓存的key的名字,支持spel
   String key() default "";
//key的生成策略,不指定可以用全局的默认的。
   String keyGenerator() default "";
   //客户选择不同的CacheManager
   String cacheManager() default "";
   //配置不同的cache resolver
   String cacheResolver() default "";
   //满足什么样的条件才能被缓存,支持SpEL,可以去到方面名,参数
   String condition() default "";
//排除哪些返回结果不加入到缓存里面去,支持SpEL,实际工作中常见的是result ==null等
   String unless() default "";
   //是否 同步读取缓存,更新缓存
   boolean sync() default false;
}

例子:

@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback")
   public Book findBook(String name)

@CachePut    

调用方法时会自动把相应的数据放入缓存,它与 @Cacheable 不同的是所有注解的方法每次都会执行,一般配置在 Update 和 Insert 方法上。看了一下源码里面的字段和用法与 @Cacheable 相同,只是使用场景不一样。

@CacheEvict:删除缓存,一般配置在删除方法上面。

public @interface CacheEvict {
//与@Cacheable相同的部分咱们就不重复叙述了。
......
//是否删除所有的实体对象
   boolean allEntries() default false;
   //是否方法执行之前执行。默认在方法调用成功之后删除
   boolean beforeInvocation() default false;
}

@Caching:所有 Cache 注解的组合配置方法,源码如下。

public @interface Caching {
   Cacheable[] cacheable() default {};
   CachePut[] put() default {};
   CacheEvict[] evict() default {};
}
  • @CacheConfig:全局 Cache 配置。
  • @EnableCaching:开启 SpringCache 的默认配置。

Spring Boot 快速开始 Demo

我们通过一个快速实例来体会一下 Spring Cache 是什么鬼,步骤如下。

第一步:pom.xml 添加 Spring Boot 的 jar 依赖:

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

第二步:添加 @EnableCaching 注解开启 Caching,实例如下。

@SpringBootApplication
@EnableJpaRepositories
@EnableCaching
public class RedisApplication {
   public static void main(String[] args) {
      SpringApplication.run(RedisApplication.class, args);
   }
}

第三步:使用的地方直接用 @Cacheable、@CachePut 等注解即可,实例如下。

@Controller
@RequestMapping("hello")
public class UserInfoController {
    @Autowired
    private UserInfoService userInfoService;
    @RequestMapping("users")
    @ResponseBody
    @Cacheable(value = "user")
    public List<UserInfoEntity> findAll(){
        System.out.println("user........");
        return userInfoService.findAll();
    }
}

结果:当我们请求第二次的时候就不会进 Controller 的这个方法里面了。此处作者只是举个例子,实际工作中,配置在 Service 层的场景比较多。

Spring Boot Cache 实现过程解析

spring.factories

1)我们都知道当引入 SpringBoot 的时候就会多一个 spring-boot-autoconfigure 的 jar,而此 Jar 里面 auto config 和加载很多相关的类。可以通过打开其包下面的 spring.factories 文件,可以看到 SpringBoot 会默认加载 org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration 配置文件。

2)spring-boot-starter-cache 这个 jar 还会帮我们加载 Spring Cache 所需要的其他 jar 包,如 spring-context 和 spring-context-support,是 Spring Cache 的核心 jar 包。

CacheAutoConfiguration 关键源码解读

@Configuration
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureBefore(HibernateJpaAutoConfiguration.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
      RedisAutoConfiguration.class })
@Import(CacheConfigurationImportSelector.class)
public class CacheAutoConfiguration {
   static final String VALIDATOR_BEAN_NAME = "cacheAutoConfigurationValidator";
   @Bean
   @ConditionalOnMissingBean
   public CacheManagerCustomizers cacheManagerCustomizers(
         ObjectProvider<List<CacheManagerCustomizer<?>>> customizers) {
      return new CacheManagerCustomizers(customizers.getIfAvailable());
   }
....}
  • 第一件事是通过 @Conditional 来判断是否满足条件进而加载 Cache 的配置文件。
  • 第二件事情是留下了很多定义和扩展的口子,如 Reids,后面章节讲。
  • 第三件事情是寻找默认的 @cache 的处理方法。

CacheConfigurationImportSelector

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

CacheType 的内容如下:

package org.springframework.boot.autoconfigure.cache;
public enum CacheType {
    GENERIC,
    JCACHE,
    EHCACHE,
    HAZELCAST,
    INFINISPAN,
    COUCHBASE,
    REDIS,
    CAFFEINE,
    /** @deprecated */
    @Deprecated
    GUAVA,
    SIMPLE,
    NONE;

    private CacheType() {
    }
}

所以,通过这两段代码,当我们没有显示手动的指定 CacheManager 或者 CacheResolver 的时候,Spring Boot Cache 会按照以下顺序查找 Cache 的默认实现者。

  • Generic
  • JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, etc)
  • EhCache 2.x
  • Hazelcast
  • Infinispan
  • Couchbase
  • Redis
  • Caffeine
  • Guava (deprecated)
  • Simple

并会自动导入各大提供商的 config。

所以这里要注意下,默认情况下 Spring 的 context-support,里面最少是有了 GUAVA 和 SIMPLE 的相关的自动 Cache 条件,看了一下源码都是仍在 Java 的自己的 JVM 中的,用的 ConcurrentHashMap 的类进行储存的。

所以你会发现我们什么都没有配置,直接开启 Cache 和引入相关的 jar 就可以直接实现 Cache 的行为。

在实际场景中有些小项目,如果只是临时做本 Aplication 的临时缓存,这种方式其实是可以考虑一下的,不一定要用很重的 Redis 分布式缓存。

后面的章节我们介绍一下 Redis 和 Spring 的 Framework 里面的 Cache 是怎么结合的。