默认 Cache 配置
当使用@EnableCachina 启动 Spring Boot 的缓存机制但又未添加其他缓存类库时,SpringBoot 会默认提供一个基 于 ConcurrentHashMap 实现的缓存组件 --ConcurrentMap-CacheManager。但官方文档已经明确提示,不建议在生产环境中使用该缓存组件。但它却是一个很好的学习缓存特性的工具。
这个默认的缓存组件是通过 SimpleCacheConfiguration 来完成自动配置的。下面,我们简单了解一下它的自动配置以及 ConcurrentMapCacheManager 的实现。
代码语言:javascript复制@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition. class)
class SimpleCacheConfiguration {
@Bean
ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties,
CacheManagerCustomizers cacheManagerCustomizers) {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager
();
List<String> cacheNames = cacheProperties . getCacheNames();
if (!cacheNames. isEmpty()) {
cacheManager . setCacheNames (cacheNames);
}
return cacheManagerCustomizers . customize(cacheManager);}
}
该 自 动 配 置 文 件 很 简 单 , 当 容 器 中 不 存 在 CacheManager 的 Bean, 同 时 满 足CacheCondition 中指定的条件时,则进行自动配置。关于 CacheCondition 中的业务逻辑实现已经在上一节进行了详细地讲解,不再赘述。
在 cacheManager 方法中首先创建了- -个 ConcurrentMapCacheManager 对象,然后通过配置属性类获得缓存名称列表,如果列表内容不为空,则赋值给上述对象 cacheManager。
最后调用 CacheManagerCustomizers 的 customize 方法对 cacheManager 进行定制化处理并返回。
下面我们重点看 cacheManager 方法中 ConcurrentMapCacheManager 类的内部实现。通过名字就可以看出它是基于 ConcurrentHashMap 来实现的,它是接口 CacheManager 的实现类,同时也实现了 BeanClassLoaderAware 接口用来获取 SerializationDelegate 及进行一些初始化操作。
首先看 ConcurrentMapCacheManager 的成员变量部分源代码。
代码语言:javascript复制public class ConcurrentMapCacheManager implements CacheManager, BeanClassLo
aderAware {
// ConcurrentMapCacheManager 缓存的基础数据结构,用于存储缓存数据 private final
ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
//是否为动态创建缓存
private boolean dynamic = true;
//是否允许 null 值
private boolean allowNullValues = true;
//是否存储 value 值
private boolean storeByValue = false;
//用于序列化的委托类,通过实现 BeanClassLoaderAware 接口注入,用子值的序列化和反
序列号
@Nullable
private SerializationDelegate serialization;
}
在 ConcurrentMapCacheManager 中定义了 ConcurrentMap<String,Cache>的成员变量,用于存储 Cache,无论后面是获取还是存储缓存类(Cache) ,都围绕该成员变量来进行操作。
这里采用的 ConcurrentHashMap 是 Java 中的一个线程安全且高效的 HashMap 实现。
dynamic 属性定义了缓存是动态创建还是静态创建,true 表示动态创建 ,false 表示静态创建,后面在涉及具体的方法功能时会用到; allowNullValues 用来表示是否允许 nul 值;
storeByValue 表示是否需要存储值, 如果需要存储值则需配合 serialization 顺序性进行序列化和反序列号操作。这里的存储值指的是复制的 value 值,与存储引用相对,存储值(复制的 value 值)时才会进行序列化和反序列化。
下面从 ConcurrentMapCacheManager 的构造方法开始来进行相关方法的讲解。
代码语言:javascript复制public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware.
//实现为空的构造方法,用于构建.
动态的 ConcurrentMapCacheManager,
//当缓存实例被请求时进行懒加裁
public ConcurrentMapCacheManager() {
//构建一个静态的 ConcurrentMapCacheManager, 仅管理捐定缓存名称的缓存
public ConcurrentMapCacheManager(Stri
, cacheNames) {
setCacheNames (Arrays . aslist(cacheNames));
/设置缓存名称或重置缓存模式
public void setCacheNames (@Nullable Collection<String> cacheNames) {
if (cacheNames != null)
for (String name : cacheNames){
this . cacheMap. put(name, createConcurrentMapCache(name));
this.dynamic = false;
} else
this . dynamic = true;
}
}
}
ConcurrentMapCacheManager 提供了两个构造方法,第一-个构造方法用于构建一个动态的 ConcurrentMapCacheManager, 构造方法实现为空。在自动配置中便是采用的该构造方法,默认情况下,dynamic 属 性的为 true,即动态构建,当缓存实例被请求时进行懒加载。
另外一个构造方法的参数为不定参数,构造方法内的核心操作就是调用 setCacheNames 方法。在 setCacheNames 方法内部, 如果 cacheNames 不为 null,也就是采用“静态”模式,会遍历缓存名称,并初始化 cacheMap 中的值。这里需要注意的是,一旦进入该业务逻辑操作,也就意味着缓存的属性及名称将被固定,运行时不会再创建其他缓存区域。
那么,如果想改变这种“不变"的情况该如何处理?还是调用该方法,设置参数 cacheNames为 null,此时执行 else 中的逻辑,将 dynamic 设置 为 true,即静态模式重置为动态模式,从而允许再次创建缓存区域。setCacheNames 方法为 public,因此不仅构造方法可以调用,其实例化对象也可以直接调用进行设置。
在第二个构造方法中调用了当前类的 createConcurrentMapCache 方法,代码如下。
代码语言:javascript复制protected Cache createConcurrentMapCache(String name) {
SerializationDelegate actualSerialization = (isStoreByValue() ? this.seri
alization : null);
return new ConcurrentMapCache(name, new ConcurrentHashMap<> (256),
isAllowNullValues(),actualSerialization);
}
createConcurrentMapCache 方法的主要作用就是创建一个 ConcurrentMapCache,它是Cache 接口的简单实现。该方法内,首先根据属性 storeByValue 的值判断是否需要Serializa-tionDelegate 来进行序列化操作,如果不需要则将 SerializationDelegate 设置为null。然后,将缓存名称、缓存值、是否允许 Nul1 值和序列化委托类当作构造参数创建Concurrent-MapCache 类并返回。
在 ConcurrentMapCacheManager 中有-个私有的 recreateCaches 方法,在满足条件的情况下会遍历 cacheMap 并调用上面 createConcurrentMapCache 方法进行缓存的重置操作。
代码语言:javascript复制private void recreateCaches() {
for (Map. Entry<String, Cache> entry : this. cacheMap .entrySet()) {
entry . setValue(createConcurrentMapCache(entry . getKey()));
}
当 allowNullValues 或 storeByValue 的值通过 set 方法改变时,均会调用 recreateCaches方法进行缓存的重置。
最后看 ConcurrentMapCacheManager 对 getCacheNames 和 getCache 方法的实现。
代码语言:javascript复制@Override
public Collection<String> getCacheNames() {
return Collections . unmodifiableSet(this . cacheMap. keySet());
@Override
@Nullable
public Cache getCache(String name) {
Cache cache = this. cacheMap . get(name);
if (cache == null && this. dynamic) {
synchronized (this . cacheMap) {cache = this. cacheMap . get(name);
if (cache == null) {
cache = createConcurrentMapCache(name);
this. cacheMap . put(name, cache);
}
}
}
return cache; }
getCacheNames 方法直接获取 cacheMap 中 name 的 Set,并通过 Collections 类将其设置为不可变的集合并返回。
getCache 方法首先根据 name 从 cacheMap 中获取 Cache 值,如果值为 null 并且是动态模式,则对 cacheMap 加锁同步, 重新获取判断,如果 cache 依旧为 null,则调用create-ConcurrentMapCache 方 法创建并给 cacheMap 赋值。否则,直接返回 cache 值。
至此,关于 ConcurrentMapCacheManager 的基本功能也讲解完毕。在此提醒一下,这只是一一个简单的 CacheManager,并没有缓存配置项,仅可用于测试环境和简单的缓存场景。
对于高级的本地缓存需求建议使用 JCacheCacheManager、EhCacheCacheManager、CaffeineCacheManager 等方法。
最后,我们再稍微拓展一下上面提到的 ConcurrentMapCache 类,该类实现了 Cache 接口,提供了缓存值的存储和获取等实现。而这些功能的实现就是围绕上面提到构造该类时传入的参数展开的。
以 ConcurrentMapCache 的 put 方法及相关方法为例,我们简单说一下它的实现过程。
代码语言:javascript复制@Override
public void put(0bject key, @Nullable object value) {
this . store. put(key, toStoreValue(value));
@Override
protected object toStoreValue (@Nullable object userValue) {
//父类的 toStoreValue 方法实现,用于检查是否允许 null。如果值为 null 且允许为 null,则返
@NullValue.
INSTANCE
Object storeValue = super . toStoreValue(userValue);
if (this. serialization != null) {
try {
//对质进行序列化
return serializeValue(this . serialization, storeValue);
catch (Throwable ex) {throw new IllegalArgumentException("Failed to serialize cache valueuserValue
"'. Does it implement Serializabl
e?", ex);
} else {
return storeValue;
//通过传入的 Serial izationDelegate 序列化缓存值为 byte 数组
private object serializeValue(SerializationDelegate serialization, object s
toreValue) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
serialization. serialize(storeValue, out);
return out. toByteArray();
} finally {
out. close();
}
上面为 put 方法涉及的操作,基本步骤是如下。
.判断待设置的 userValue 的值是否为 mull,如果为 mull 且允许存储 mull 值,返回NullValue.INSTANCE。否则,直接返回 userValue 值。以上值均赋值给 storeValue。
如果序列化委托类(serialization )不为 null, 则通过 SerializationDelegate 对 storeValue 值进行序列化操作。如果为 mull,则直接返回 store Value。
.无论经过以上步骤获得的是原始传入值、NullValue.INSTANCE 或是经过序列化的字节数组 , 都 通 过 store 的 put 方 法 将 其 存 储 。store 的数 据 结 构 为Concurrent-Map<Object,Object>,就是我们创建 ConcurrentMapCache 时传入的参数之一。
其中第一个 Object 为缓存的 key,第二个 Object 为缓存的具体数据。
至此,Spring Boot 默认,的。Cache 配置就讲解完毕了,关于滚动鼠标轴或单击,开始截长图 ConcurrentMapCache 类的其他方法实现,读者朋友可自行阅读相关源码,不过基本上都是围绕上面提到的一-些属性和数据结构展开的。
小结
本章重点介绍了 Spring Boot 中缓存的自动配置以及基于 ConcurrentHashMap 实现的最简单 的 缓 存 功 能 。涉 及 的 缓 存 实 现 都 只 是 基 于 Java 提 供 的 数 据 结 构 (Collection 、ConcurrentHashMap) 存储来实现的。而在实战过程中,根据不同的场景会使用不同的三方缓存组件,比如 JCache、EhCache、Caffeine、 Redis 等。但基本的实现原理一致,读者朋友可参照本章内容进行具体的分析学习。
本文给大家讲解的内容是SpringBootCache源码解析:默认Cache配置
- 下篇文章给大家讲解的是Spring Boot 日志源码解析;
- 觉得文章不错的朋友可以转发此文关注小编;
- 感谢大家的支持!
本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。