今天来实现以下SpringBoot Cache 支持多类型Cache缓存,今天整合一下Redis 和 Caffeine两个类型的缓存模式,也基本满足现在项目的使用需求,一个是可以是做分布式缓存,一个用来做本地缓存,足以应对大部分的场景了。
CompositeCacheManager
Spring 框架玩的就是各种设计模式,为我们提供了很多灵活可扩展的机制和接口,在整合多Cache缓存类型之前,我们需要先来看一下 org.springframework.cache.support.CompositeCacheManager
这个类。我简单的贴出几个实现方法。
public class CompositeCacheManager implements CacheManager, InitializingBean {
private final List<CacheManager> cacheManagers = new ArrayList<>();
public CompositeCacheManager(CacheManager... cacheManagers) {
setCacheManagers(Arrays.asList(cacheManagers));
}
@Override
@Nullable
public Cache getCache(String name) {
for (CacheManager cacheManager : this.cacheManagers) {
Cache cache = cacheManager.getCache(name);
if (cache != null) {
return cache;
}
}
return null;
}
@Override
public Collection<String> getCacheNames() {
Set<String> names = new LinkedHashSet<>();
for (CacheManager manager : this.cacheManagers) {
names.addAll(manager.getCacheNames());
}
return Collections.unmodifiableSet(names);
}
同样继承了 CacheManager
接口,但是没有任何Cache类型的实现。这就是经典的组合模式。具体的我就不多说了,如果感兴趣可以自己去查看一下相关资料。
接下来我们要做的就是 使用这个组合模式的 CompositeCacheManager
来实现多CacheManager。
实现CompositeCacheConfiguration
因为SpringBoot没有CompositeCacheManager
的自动装配的实现(我是没找到),因此我们需要自己去实现一个 CompositeCacheManager
的自动装配。先贴代码,具体解释在注解中
@Configuration(proxyBeanMethods = false)
//类条件装配,在有CompositeCacheManager类的时候装配
@ConditionalOnClass(CompositeCacheManager.class)
//装配条件,必须在CacheAutoConfiguration 这个类之后,
//因为在装配非原生支持的Cache类的时候需要用到其中注入的的Bean
@AutoConfigureAfter(CacheAutoConfiguration.class)
//自定义条件实现
@Conditional({ CompositeCacheCondition.class })
public class CompositeCacheConfiguration {
//声明 CompositeCacheManager Bean 并标注@Primary表示Spring依赖注入或者查找到这个Bean
//参数CacheManager的数组,让Spring 框架依赖查找到所有CacheManager实现类,并构造到CompositeCacheManager中
@Bean
@Primary
public CompositeCacheManager compositeCacheManager(CacheManager[] cacheManagers) {
return new CompositeCacheManager(cacheManagers);
}
//简单的自定义外部化配置类,用来配置非原生缓存的相关信息
@Bean
public TestCacheProperties testCacheProperties() {
return new TestCacheProperties();
}
}
TestCacheProperties
@ConfigurationProperties(prefix = "test")
public class TestCacheProperties {
//缓存类型 参考 org.springframework.boot.autoconfigure.cache.CacheProperties
//这里就简单实现了缓存类型的变量
private CacheType type;
public CacheType getType() {
return type;
}
public void setType(CacheType type) {
this.type = type;
}
}
上面两段代码其实很简单,就是SpringBoot的自动装配基础和外部化配置的基础,重点是在 CompositeCacheCondition
条件装配代码中。在上一篇 我详细介绍了org.springframework.boot.autoconfigure.cache.CacheCondition
如果觉得迷惑,可以先看上一篇
SpringBoot Cache整合多CacheManager(一)。现在我们看看 CompositeCacheCondition
的实现
public class CompositeCacheCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
String sourceClass = "";
if (metadata instanceof ClassMetadata) {
sourceClass = ((ClassMetadata) metadata).getClassName();
}
ConditionMessage.Builder message = ConditionMessage.forCondition("Cache", sourceClass);
Environment environment = context.getEnvironment();
try {
//获取spring cache 原生的缓存类型
BindResult<CacheType> specified = Binder.get(environment).bind("spring.cache.type", CacheType.class);
//获取自定义的缓存类型
BindResult<CacheType> testCache = Binder.get(environment).bind("test.type", CacheType.class);
//如果一致 则执行自动化配置,使用spring cache原生的缓存类型
if (specified.get() == testCache.get()) {
return ConditionOutcome.noMatch(message.because("cache type is same"));
}
//如果不一致,则执行自动化配置 初始化CompositeCacheManager
return ConditionOutcome.match(message.because("cache type is different"));
}
catch (BindException ex) {
}
return ConditionOutcome.noMatch(message.because("unknown cache type"));
}
}
具体解释在代码的注解中,简单来说就是判断原生缓存和自定义缓存是否使用同一类型,如果使用同一类型则不进行自动装配。
实现CaffeineCompositeCacheConfiguration
接下来我们来实现自定义缓存类别为Caffeine的装配,注意原生的装配类 是CompositeCacheManager
,这里为了体现CacheManager
组合模式的装配,自定义的装配类就起名为 CaffeineCompositeCacheConfiguration
。实现的代码与原生的基本一致,只有装配条件有不同。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Caffeine.class, CaffeineCacheManager.class })
@AutoConfigureAfter(CacheAutoConfiguration.class)
//自定义的条件装配类
@Conditional({ CaffeineCompositeCacheCondition.class })
public class CaffeineCompositeCacheConfiguration {
//原生方法copy过来的
@Bean("caffeineCacheManager")
CaffeineCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers customizers,
ObjectProvider<Caffeine<Object, Object>> caffeine, ObjectProvider<CaffeineSpec> caffeineSpec,
ObjectProvider<CacheLoader<Object, Object>> cacheLoader) {
CaffeineCacheManager cacheManager = createCacheManager(cacheProperties, caffeine, caffeineSpec, cacheLoader);
List<String> cacheNames = cacheProperties.getCacheNames();
if (!CollectionUtils.isEmpty(cacheNames)) {
cacheManager.setCacheNames(cacheNames);
}
return customizers.customize(cacheManager);
}
//原生方法copy过来的
private CaffeineCacheManager createCacheManager(CacheProperties cacheProperties,
ObjectProvider<Caffeine<Object, Object>> caffeine, ObjectProvider<CaffeineSpec> caffeineSpec,
ObjectProvider<CacheLoader<Object, Object>> cacheLoader) {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
setCacheBuilder(cacheProperties, caffeineSpec.getIfAvailable(), caffeine.getIfAvailable(), cacheManager);
cacheLoader.ifAvailable(cacheManager::setCacheLoader);
return cacheManager;
}
//原生方法copy过来的
private void setCacheBuilder(CacheProperties cacheProperties, CaffeineSpec caffeineSpec,
Caffeine<Object, Object> caffeine, CaffeineCacheManager cacheManager) {
String specification = cacheProperties.getCaffeine().getSpec();
if (StringUtils.hasText(specification)) {
cacheManager.setCacheSpecification(specification);
}
else if (caffeineSpec != null) {
cacheManager.setCaffeineSpec(caffeineSpec);
}
else if (caffeine != null) {
cacheManager.setCaffeine(caffeine);
}
}
}
主要看一下自定义的条件装配类 CaffeineCompositeCacheCondition
public class CaffeineCompositeCacheCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
String sourceClass = "";
if (metadata instanceof ClassMetadata) {
sourceClass = ((ClassMetadata) metadata).getClassName();
}
ConditionMessage.Builder message = ConditionMessage.forCondition("Cache", sourceClass);
Environment environment = context.getEnvironment();
try {
//获取spring cache 原生的缓存类型
BindResult<CacheType> specified = Binder.get(environment).bind("spring.cache.type", CacheType.class);
//获取自定义的缓存类型
BindResult<CacheType> testCache = Binder.get(environment).bind("test.type", CacheType.class);
//如果原生Cache缓存与自定义的缓存相同,则不进行装配
if (specified.get() == testCache.get()) {
return ConditionOutcome.noMatch(message.because("cache type is same"));
}
//如果自定义的为 Caffein,则进行装配
if(CacheType.CAFFEINE == testCache.get())
return ConditionOutcome.match(message.because("cache type is redis"));
}
catch (BindException ex) {
}
return ConditionOutcome.noMatch(message.because("unknown cache type"));
}
}
代码其实跟 之前的自定义装配CompositeCacheConfiguration条件相同,但是主要做了类型的判断。总体来说就是,获取到 原生Spring Cache 类别和自定义Cache,如果相同就不加载,不同并且类型是 Caffein就加载。
实现RedisCompositeCacheConfiguration
代码其实跟 CaffeineCompositeCacheConfiguration
一个原理,就不多做解释了。大体贴上代码
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisConnectionFactory.class)
@AutoConfigureAfter(CacheAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@Conditional(RedisCompositeCacheCondition.class)
public class RedisCompositeCacheConfiguration {
@Bean("redisCacheManager")
RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers,
ObjectProvider<RedisCacheConfiguration> redisCacheConfiguration,
ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers,
RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(
determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
}
redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return cacheManagerCustomizers.customize(builder.build());
}
private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(
CacheProperties cacheProperties,
ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
ClassLoader classLoader) {
return redisCacheConfiguration.getIfAvailable(() -> createConfiguration(cacheProperties, classLoader));
}
private org.springframework.data.redis.cache.RedisCacheConfiguration createConfiguration(
CacheProperties cacheProperties, ClassLoader classLoader) {
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration
.defaultCacheConfig();
config = config.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
public class RedisCompositeCacheCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
String sourceClass = "";
if (metadata instanceof ClassMetadata) {
sourceClass = ((ClassMetadata) metadata).getClassName();
}
ConditionMessage.Builder message = ConditionMessage.forCondition("Cache", sourceClass);
Environment environment = context.getEnvironment();
try {
//获取spring cache 原生的缓存类型
BindResult<CacheType> specified = Binder.get(environment).bind("spring.cache.type", CacheType.class);
//获取自定义的缓存类型
BindResult<CacheType> testCache = Binder.get(environment).bind("test.type", CacheType.class);
//如果原生Cache缓存与自定义的缓存相同,则不进行装配
if (specified.get() == testCache.get()) {
return ConditionOutcome.noMatch(message.because("cache type is same"));
}
//如果自定义的为 redis,则进行装配
if(CacheType.REDIS == testCache.get())
return ConditionOutcome.match(message.because("cache type is redis"));
}
catch (BindException ex) {
}
return ConditionOutcome.noMatch(message.because("unknown cache type"));
}
}
到这里我们代码就写完了,接下来该配置SpringBoot自动装配信息了。
配置spring.factories
熟悉SpringBoot的小伙伴都知道 SpringBoot的很多初始化自动装配都在 spring.factories中配置,由SpringBoot统一管理和装配加载。
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.cachetest.config.CompositeCacheConfiguration,\
com.example.cachetest.config.RedisCompositeCacheConfiguration,\
com.example.cachetest.config.CaffeineCompositeCacheConfiguration
将我们配置好的三个自动装配类,加载到我们项目的META-INF
资源文件夹下。如图

配置自定义缓存外部化配置
配置 application.yml
# 原生Spring boot Cache配置
spring:
cache:
type: redis # 设置为Redis缓存
redis:
host: x.x.x.x # 自己启动redis 配置其IP
#自定义缓存
test:
type: Caffeine #配置为Caffeine
启动查看结果
我们启动一下项目来看看是否实现了多CacheManager的实现。如图。

我们可以看到了组合模式实现的CompositeCacheManager
已经可以注入到Spring框架中,并且实现了多个CacheManager的实现。
小结
可能写的有点乱,对于不怎么熟悉SpringBoot机制的小伙伴看起来有点吃力,这里有一个小坑,就是Spring Cache在实现CacheManager中的所有类中,都想到了 CacheName 为空的情况,所以如果你没声明CacheName 他们会自己创建,这样细心的小伙伴就会发现,即使我们实现了多类型缓存,但是在 CompositeCacheManager中 会总返回第一个缓存Manage,个人建议自己在实现一个 CompositeCacheManager,在getCache方法中添加 CacheName 的遍历校验,这样就可以根据我们定义的不同CacheName 选择不同的缓存去操作了。
网友评论