改造
在 v3
版本中,我们使用自定义注解的方式实现了两级缓存通过 一个注解 管理的功能。本文我们换一种方式,直接通过扩展spring提供的接口来实现这个功能,在进行整合之前,我们需要简单了解一下 JSR107
缓存规范。
JSR107 规范
在 JSR107
缓存规范中定义了5个核心接口,分别是 CachingProvider
, CacheManager
, Cache
, Entry
和 Expiry
,参考下面这张图,可以看到除了 Entry
和 Expiry
以外,从上到下都是一对多的包含关系。
![](https://img-blog.csdnimg.cn/img_convert/d594e611a3b77ba890da6aa7ba184642.webp?x-oss-process=image/format,png)
从上面这张图我们可以看出,一个应用可以创建并管理多个 CachingProvider
,同样一个 CachingProvider
也可以管理多个 CacheManager
,缓存管理器 CacheManager
中则维护了多个 Cache
。
Cache
是一个类似 Map
的数据结构, Entry
就是其中存储的每一个 key-value
数据对,并且每个 Entry
都有一个过期时间 Expiry
。而我们在使用spring集成第三方的缓存时,只需要实现 Cache
和 CacheManager
这两个接口就可以了,下面分别具体来看一下。
Cache
spring中的 Cache
接口规范了缓存组件的定义,包含了缓存的各种操作,实现具体缓存操作的管理。例如我们熟悉的 RedisCache
、 EhCacheCache
等,都实现了这个接口。
![](https://img-blog.csdnimg.cn/img_convert/9ab8286192ab4717c349a44ed75930f8.webp?x-oss-process=image/format,png)
在 Cache
接口中,定义了 get
、 put
、 evict
、 clear
等方法,分别对应缓存的存入、取出、删除、清空操作。不过我们这里不直接使用 Cache
接口,上面这张图中的 AbstractValueAdaptingCache
是一个抽象类,它已经实现了 Cache
接口,是spring在 Cache
接口的基础上帮助我们进行了一层封装,所以我们直接继承这个类就可以。
继承 AbstractValueAdaptingCache
抽象类后,除了创建 Cache
的构造方法外,还需要实现下面的几个方法:
// 在缓存中实际执行查找的操作,父类的get()方法会调用这个方法 protected abstract Object lookup(Object key); // 通过key获取缓存值,如果没有找到,会调用valueLoader的call()方法 public <T> T get(Object key, Callable<T> valueLoader); // 将数据放入缓存中 public void put(Object key, Object value); // 删除缓存 public void evict(Object key); // 清空缓存中所有数据 public void clear(); // 获取缓存名称,一般在CacheManager创建时指定 String getName(); // 获取实际使用的缓存 Object getNativeCache();
因为要整合 RedisTemplate
和 Caffeine
的 Cache
,所以这些都需要在缓存的构造方法中传入,除此之外构造方法中还需要再传出缓存名称 cacheName
,以及在配置文件中实际配置的一些缓存参数。先看一下构造方法的实现:
public class DoubleCache extends AbstractValueAdaptingCache { private String cacheName; private RedisTemplate<Object, Object> redisTemplate; private Cache<Object, Object> caffeineCache; private DoubleCacheConfig doubleCacheConfig; protected DoubleCache(boolean allowNullValues) { super(allowNullValues); } public DoubleCache(String cacheName,RedisTemplate<Object, Object> redisTemplate, Cache<Object, Object> caffeineCache, DoubleCacheConfig doubleCacheConfig){ super(doubleCacheConfig.getAllowNull()); this.cacheName=cacheName; this.redisTemplate=redisTemplate; this.caffeineCache=caffeineCache; this.doubleCacheConfig=doubleCacheConfig; } //... }
抽象父类的构造方法中只有一个 boolean
类型的参数 allowNullValues
,表示是否允许缓存对象为 null
。除此之外, AbstractValueAdaptingCache
中还定义了两个包装方法来配合这个参数进行使用,分别是 toStoreValue
和 fromStoreValue
,特殊用途是用于在缓存 null
对象时进行包装、以及在获取时进行解析并返回。
我们之后会在 CacheManager
中调用后面这个自己实现的构造方法,来实例化 Cache
对象,参数中 DoubleCacheConfig
是使用 @ConfigurationProperties
读取的yml配置文件封装的数据对象,会在后面使用。
当一个方法添加了 @Cacheable
注解时,执行时会先调用父类 AbstractValueAdaptingCache
中的 get(key)
方法,它会再调用我们自己实现的 lookup
方法。在实际执行查找操作的 lookup
方法中,我们的逻辑仍然是先查找 Caffeine
、没有找到时再查找 Redis
:</