前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBootCache源码解析:Cache自动配置

SpringBootCache源码解析:Cache自动配置

作者头像
愿天堂没有BUG
发布2022-10-28 16:23:23
1.3K0
发布2022-10-28 16:23:23
举报
文章被收录于专栏:愿天堂没有BUG(公众号同名)

SpringBootCache源码解析

Spring Boot 支持了多种缓存的自动配置,其中包括 Generic、JCache、EhCache 2.x、Hazelcast、 Infinispan、 Couchbase、 Redis 、Caffeine 和 Simple。早期版本还支持Guava 的缓存,但目前已经废弃。本章将重点讲解缓存的自动配置 CacheAutoConfiguration和默认的 SimpleCacheConfiguration 自动配置及相关内容。

Cache 简介

随着项目的发展,往往会出现- -些瓶颈, 比如与数据库的交互、与远程服务器的交互等。

此时,缓存便派上了用场。而在 Spring 3.1 中引入了基于注解的 Cache 的支持在spring-context 包 中 定 义 了 org.springframework.cache. CacheManager 和org.springframework.cache.Cache 接口,用来统一-不同的缓存的技术。

CacheManager 是 Spring 提供的各种缓存技术管理的抽象接口,而 Cache 接口包含缓存的增加、删除、读取等常用操作。针对 CacheManager, Spring 又提供了多种实现,比如基于Collection 来 实 现 的 SimpleCacheManager 、 基 于 ConcurrentHashMap 实 现 的 Concurrent-MapCacheManager、基于 EhCache 实现的 EhCacheCacheManager 和基于JCache 标准实现的 JCacheCacheManager 等。

Spring Cache 的实现与 Spring 事务管理类似,都是基于 AOP 的方式。其核心思想是:第一次调用缓存方法时,会把该方法参数和返回结果作为键值存放在缓存中,当同样的参数再次请求方法时不再执行该方法内部业务逻辑,而是直接从缓存中获取结果并返回。

Spring Cache 提供了@CacheConfig、@Cacheable 、@CachePut 、@CacheEvict 等注解来完成缓存的透明化操作,相关功能如下。.@CacheConfig:用于类上,缓存一些公共设置。

.@Cacheable:用于方法上,根据方法的请求参数对结果进行缓存,下次读取时直接读取缓存内容。

.@CachePut: 用于方法上,能够根据方法的请求参数对其结果进行缓存,和@Cacheable不同的是,它每次都会触发真实方法的调用。

.@CacheEvict: 用于方法上,清除该方法的缓存,用在类上清除整个类的方法的缓存。

在了解了 Spring Cache 的基本作用的和定义之后,下面来看在 SpringBoot 中是如何对Cache 进行自动配置的。

Cache 自动配置

在 Spring Boot 中,关于 Cache 的默认自动配置类只有 CacheAutoConfiguration,主要用于缓存抽象的自动配置,当通过@EnableCaching 启用缓存机制时,根据情况可创建CacheManager。对于缓存存储可以通过配置自动检测或明确指定。

CacheAutoConfiguration 同样在 ME TA-INF/spring.factories 文件中配置注册。

代码语言:javascript
复制
# . Auto Configure
org. springframework. boot. autoconfigure . EnableAutoConfiguration=\
org. springframework . boot . autoconfigure . cache . CacheAutoConfiguration, \
下面先来看 CacheAutoConfiguration 类的注解部分代码实现。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass (CacheManager. class)
@ConditionalOnBeanCacheAspectSupport . class)
@ConditionalOnMissingBean(value = CacheManager .class, name = "cacheResolve
r")
@EnableConfigurationProperties (CacheProperties. class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration. class, HazelcastAutoConfig
uration. class,
HibernateJpaAutoConfiguration. class, RedisAutoConfigur
ation.class })
@Import(CacheConfigurat ionImportSelector.class)
public class CacheAutoConfiguration {
}

@ConditionalOnClass 指 定 需 要 在 classpath 下 存 在 CacheManager 类 。关 于CacheManager 类是一个缓存管理器的接口,管理各种缓存(Cache) 组件。针对不同的缓存技术,会有不同的实现类。比如,在 Spring 中提供了 SimpleCacheManager (测试)、 Concurrent-MapCacheManager ( 默 认 ) 、 NoOpCacheManager ( 测 试 ) 、EhCacheCacheManager ( 基于 EhCache)、RedisCacheManager (基于 Redis) 等实现类。CacheManager 接口提供了两个方法:根据名称获取缓存和获取缓存名称集合,相关代码如下。

代码语言:javascript
复制
public interface CacheManager {
//根据名称获取缓存
@Nullable
Cache getCache(String name);
//获取缓存名称集合
Collection<String> getCacheNames();
}

在 CacheManager 接 口 中 只 定 义 了 上 面 两 个 方 法 , 但 在 其 抽 象 实 现 类AbstractCache-Manager 中便扩展了新增 Cache、更新 Cache 等方法。

@ConditionalOnBean 指定需要存在 CacheAspectSupport 的 Bean 时才生效,换句话说,就 是 需要在使用 了 @EnableCaching 时 才 有 效 。这 是 因 为 该 注 解 隐 式 的 导 致 了CacheInter-ceptor 对应的 Bean 的初始化,而 CacheInterceptor 为 CacheAspectSupport的子类。

@ConditionalOnMissingBean 指定名称为 cacheResolver 的 CacheManager 对象不存在时生效。

@ EnableConfigurationProperties 加 载 缓 存 的 CacheProperties 配 置 项 , 配 置 前 缀 为spring.cache.

@AutoConfigureAfter 指定该自动配置必须在缓存数据基础组件自动配置之后进行,这里包括 Couchbase、 Hazelcast、 HibernateJpa 和 Redis 的自动配置

想要实现缓存,需要先集成对应的缓存框架或组件。这里以 Redis 为例,它的自动配置类RedisAutoConfiguration 中 便 完 成 了 Redis 相 关 的 Redis Template 和 StringRedisTemplate 的实例化。而 RedisAutoConfiguration 中导入类 JedisConnectionConfiguration又完成了 Redis 使用 Jedis 连接的配置

@Ilmport 导入 CacheConfigurationlmportSelector,其实是导入符合条件的 Spring Cache 使用的各类基础缓存框架(或组件)的配置。该类的实现就位于 CacheAutoConfiguration 中,代码如下。

代码语言:javascript
复制
static class CacheConfigurat ionImportSelector implements ImportSelector {
public String[] selectImports(Annotat ionMetadata importingClassMetadata)
CacheType[] types = CacheType.values();
for(inti=0:i<types.leneth:1t+)5
imports[i] = CacheConfigurations . getConfigurat ionClass(types[i]);
return imports; }
}

导入类的获取是通过实现 ImportSelector 接口来完成的,具体获取步骤位于 selectlmports方法中。该方法中,首先获取枚举类 CacheType 中定义的缓存类型数据,CacheType 中定义支 持的缓存类型如下。

代码语言:javascript
复制
//支持的缓存类型(按照优先级定义)
public enum CacheType {
//使用上下文中的 Cache Bean 进行通用缓存
GENERIC,
// JCache(JSR- 107) 支持的缓存
JCACHE,
// EhCache 支持的缓存
EHCACHE,
// Hazelcast 支持的缓存
HAZELCAST,
// Infinispan 支持的缓存
INFINISPAN,
// Couchbase. 支持的缓存
COUCHBASE,
// Redis.支持的缓存
REDIS,
// Caffeine 支持的缓存
CAFFEINE,
//内存基本的简单缓存
SIMPLE,
// 不支持缓存
NONE
}

枚举类 CacheType 中定义了以上支持的缓存类型,而且上面的缓存类型默认是按照优先级从前到后的顺序排列的。

selectlmports 方法中,当获取 Cache Type 中定义的缓存类型数组之后,遍历该数组并通过CacheConfigurations 的 getConfigurationClass 方法获得每种类型缓存对应的自动配置类( 注解@Configuration 的类)。

CacheConfigurations 相关代码如下。

代码语言:javascript
复制
final class CacheConfigurations {
private static final Map<CacheType, Class<?>> MAPPINGS;
//定义 CacheType 5@Conf iguration 之间的对应关系
static {
Map<CacheType, Class<?>> mappings = new EnumMap<>(CacheType.class);
mappings . put(CacheType . GENERIC, GenericCacheConfiguration.class);mappings . put(CacheType . EHCACHE,EhCacheCacheConfiguration. class);
mappings . put (CacheType . HAZELCAST, HazelcastCacheConfiguration. class);
mappings . put(CacheType . INF INISPAN, InfinispanCacheConfiguration.class);
mappings . put (CacheType . JCACHE, JCacheCacheConfiguration. class);
mappings . put (CacheType . COUCHBASE, CouchbaseCacheConfiguration. class);
mappings . put (CacheType. REDIS, RedisCacheConfiguration.class);
mappings . put(CacheType . CAFFEINE, CaffeineCacheConfiguration. class);
mappings . put(CacheType . SIMPLE, SimpleCacheConfiguration.class);
mappings . put(CacheType . NONE, NoOpCacheConfiguration.class);
MAPPINGS = Collections . unmodifiableMap(mappings);
//根据 CacheType
型获得对应的@Configuration 类
static String getConfigurationClass(CacheType cacheType) {
Class<?> configurationClass = MAPPINGS . get(cacheType);
Assert.state(configurationClass != null, () -> "Unknown cache type ”+
cacheType) ;
return configurationClass . getName();
}
}

经过以上步骤,我们会发现通过@Import 注解,CacheAutoConfiguration 导入了 CacheType中定义的所有类型的自动配置,也就是 Spring Boot 目前支持的缓存类型。而具体会自动配置哪种类型的缓存,还需要看导入的自动配置类里面的生效条件。

我们以 GenericCacheConfiguration 为例进行了解,源代码如下。

代码语言:javascript
复制
@Configurat ion(proxyBeanMethods = false)
@ConditionalOnBean(Cache . class)
@ConditionalOnMissingBean(CacheManager . class)
@Conditional(CacheCondition. class)
class GenericCacheConfiguration {
@Bean
SimpleCacheManager cacheManager(CacheManagerCustomizers customizers, Coll
ection
<Cache> caches) {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager . setCaches(caches);
return customizers . customize(cacheManager);
}}

在 GenericCacheConfiguration 的注解部分,@ConditionalOnBean 指定当 Cache 的 Bean存在时进行实例化操作,@ConditionalOnMissingBean 指定当 CacheManager 的 Bean 不存在时进行实例化操作,@Conditional 指定当满足 CacheCondition 指定的条件时进行实例化操作。

CacheManager 我们前面已经介绍过,不再赘述。Cache 是一 个定义了缓存通用操作的接口,其中定义了缓存名称获取、缓存值获取、清除缓存、添加缓存值等操作。对应的缓存组件或框架实现该接口,并根据组件自身的情况提供对应的操作方法实现。

下面看 CacheCondition 类中定义的条件。

代码语言:javascript
复制
class CacheCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome (ConditionContext context, Annot
ated-
TypeMetadata metadata) {
String sourceClass =
if (metadata instanceof ClassMetadata) {
sourceClass = ((ClassMetadata) metadata). getClassName() ;
Condit ionMessage . Builder message = ConditionMessage . forCondition("C
ache" ,
sourceClass);
Environment environment = context . getEnvironment( ) ;
try {
//创建指定环境的 Binder, 然后绑定属性到对象上
BindResult<CacheType> specified = Binder . get( environment) . bind
("spring.
cache. type", CacheType . class);
//如果未绑定,则返回匹配
if (!specified. isBound()) {
return ConditionOutcome . match(message . because("automatic ca
che type"));
//获取所需的缓存类型
CacheType required = CacheConfigurations . getType(((AnnotationMe
tadata)
metadata) . getClassName());
//如果已绑定,并且绑定的类型与所需的缓存类型相同,则返回匹配
if (specified.get() == required) {return Conditi onOutcome . match(message. because(specified. get
() +”cache type"));
} catch (BindException ex) {
//其他情况则返回不匹配
return ConditionOutcome . noMatch(message . because("unknown cache typ
e"));
}
}

CacheCondition 的核心逻辑就是首先通过 Binder 进行指定属性和类的绑定,然后通过绑定结果( BindResult)进行判断:如果判断结果是未绑定,则直接返回条件匹配;否则,判断绑定的缓存类型与所需的缓存类型是否相等,如果相等则返回条件匹配;其他情况则返回条件不匹配。

当 GenericCacheConfiguration 满足注解指定的条件后,便会通过 cacheManager 方法进行SimpleCacheManager 类的实例化操作。首先创建 SimpleCacheManager 对象,然后将缓存 集 合 设 置 到 对 象 中 , 最 后 通 过 CacheManagerCustomizers 的 customize 方 法 对SimpleCacheManager 进行定制化处理。

SimpleCacheManager 类是接口 CacheManager 的一个实现类,通过集合来实现缓存功能,源代码如下。

代码语言:javascript
复制
public class SimpleCacheManager extends AbstractCacheManager {
private Collection<? extends Cache> caches = Collections . emptySet();
//设置缓存集合
public void setCaches (Collection<? extends Cache> caches) {
this.caches = caches;
//获取缓存集合
@Override
protected Collection<? extends Cache> loadCaches() {
return this. caches;
}
}

通过以上代码可以看出,SimpleCacheManager 的实现极其简单, 就是基于 Cache 的集合来实现的,它提供了设置缓存集合和获取缓存集合的方法。同样,由于实现比较简单,它往往被用于测试环境和简单缓存场景中。

上面我们以 GenericCacheConfiguration 为例讲解了@Import 引入的缓存组件配置,关于其他的类型缓存注解的配置就不再一-讲解了。

下 我 继 看 @Ilmport 的 CacheManagerEntityManagerFactoryDependsOnPostProcess-or类。该类同样为 CacheAutoConfiguration 的内部类。

代码语言:javascript
复制
@ConditionalOnClass(LocalContainerEnt ityManagerFactoryBean. class)
@Conditiona lOnBean(AbstractEntityManagerFactoryBean.class)
static class CacheManagerEntityManagerFactoryDependsOnPostProcessor
extends EntityManagerFactoryDependsOnPostProcessor {
CacheManagerEntityManagerFactoryDependsOnPostProcessor() {
super("cacheManager");}
}

该 类 实 现 了 EntityManagerFactoryDependsOnPostProcessor, 本质上是BeanFactoryPost-Processor 的 一 个 实 现 类 。当 classpath中存在LocalContainerEntityManagerFactoryBean类和实现了抽象类AbstractEntityManagerFactoryBean 的类的 Bean 时,才会进行实例化操作。

在该类的构造方法中调用父类构造方法并传递值"cacheManager”。因此,动态声明了所有类型为 EntityManagerFactory 的 Bean 都必须依赖于名称为 cacheManager 的 Bean。

最后,我们看一下 CacheAutoConfiguration 中其余的代码。

代码语言:javascript
复制
//实例化 CacheManagerCus tomizers
@Bean
@ConditionalOnMissingBean
public CacheManagerCustomizers cacheManagerCustomizers(
ObjectProvider<CacheManagerCustomizer<?>> customizers) {
return new CacheManagerCustomizers(
customi zers . orderedStream(). collect(Collectors . toList()));
}

cacheManagerCustomizers 方法初始化了 CacheManagerCustomizers 对象的 Bean,主要是 将 容 器 中 存 在 的 一 一 个 或 多 个 CacheManagerCustomizer 的 Bean 组 件 包 装 为CacheManager-Customizers,并将 Bean 注入容器。

代码语言:javascript
复制
//实例化 CacheManagerVal idator
@Bean
public CacheManagerValidator cacheAutoConfigurat ionValidator(CachePropert
ies-
cachePropert
ies,
objectProvid
er<CacheManager> cacheManager) {
return new CacheManagerValidator(cacheProperties, cacheManager);
// CacheManagerVal idator 的具体定义,用于检查并抛出有意义的异常 static class
CacheManagerValidator implements InitializingBean {private final CacheProperties cacheProperties;
private final objectProvider<CacheManager> cacheManager;
CacheManagerValidator(CacheProperties cacheProperties, objectProviderkCac
he-
Manager> cacheManager) {
this. cacheProperties = cacheProperties;
this. cacheManager = cacheManager;
}
@Override
public void afterPropertiesSet() {
Assert . notNull(this . cacheManager . getIfAvailable(),
) -> "No cache manager could be auto- configured, check
your configuration (caching ”+ "type is” + this. cacheProperties .getType
() + "')");
}
}

cacheAutoConfigurationValidator 方法初始化了 CacheManagerValidator 的 Bean,该 Bean用于确保容器中存在一个 CacheManager 对象, 以达到缓存机制可以继续被配置和使用的目的,同时该 Bean 也用来提供有意义的异常声明。

至此关于 Spring Boot 中 cache 的 CacheAutoConfiguration 自动配置讲解完毕,随后我们会继续讲一下Spring Boot 中默认的自动配置。

本文给大家讲解的内容是SpringBootCache源码解析:Cache自动配置

  1. 下篇文章给大家讲解的是SpringBootCache源码解析:默认Cache配置;
  2. 觉得文章不错的朋友可以转发此文关注小编;
  3. 感谢大家的支持!

本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 愿天堂没有BUG 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SpringBootCache源码解析
  • Cache 简介
  • Cache 自动配置
  • 本文给大家讲解的内容是SpringBootCache源码解析:Cache自动配置
相关产品与服务
云数据库 Redis®
腾讯云数据库 Redis®(TencentDB for Redis®)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档