我们都知道有经验的程序员,如果配置缓存,当用到 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 定义了 org.springframework.cache.CacheManager 和 org.springframework.cache.Cache 接口用来统一不同的缓存的技术。我们打开 CachingConfigurer 也会发现主要的几个东西:
public interface CachingConfigurer {
CacheManager cacheManager();
CacheResolver cacheResolver();
KeyGenerator keyGenerator();
CacheErrorHandler errorHandler();
}
Cache 接口包含缓存的各种操作(增加、删除、获得缓存)。Spring 的 Framework 又做了好多,Cache 的默认的缓存的实现如下:
其中包括:Ehcache、GuavaCache 等很多流行的 Cache 的实现,而 RedisCache 的实现我们后面讲。我们打开 CacheManager 看一下:
public interface CacheManager {
Cache getCache(String name);
Collection<String> getCacheNames();
}
所以 CacheManager 是 Spring 提供的各种缓存技术抽象接口,通过它管理 Spring Framework 里面默认的实现的 CacheManager 有如下内容:
CacheResolver 解析器,用于根据实际情况来动态解析使用哪个 Cache,默认实现的有如下:
KeyGenerator 当我们使用 Cache 注解的时候,默认 key 的生成规则:
@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 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 层的场景比较多。
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 包。
@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 的处理方法。
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 是怎么结合的。