美文网首页
SpringBoot Cache整合多CacheManager(

SpringBoot Cache整合多CacheManager(

作者: NealLemon | 来源:发表于2020-11-22 11:13 被阅读0次

今天来实现以下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资源文件夹下。如图

META-INF.jpg

配置自定义缓存外部化配置

配置 application.yml

# 原生Spring boot Cache配置 
spring:
  cache:
    type: redis    # 设置为Redis缓存
  redis:
    host: x.x.x.x   # 自己启动redis 配置其IP
#自定义缓存    
test:
  type: Caffeine    #配置为Caffeine

启动查看结果

我们启动一下项目来看看是否实现了多CacheManager的实现。如图。

多CacheManager.jpg

我们可以看到了组合模式实现的CompositeCacheManager已经可以注入到Spring框架中,并且实现了多个CacheManager的实现。

小结

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

demo地址

相关文章

网友评论

      本文标题:SpringBoot Cache整合多CacheManager(

      本文链接:https://www.haomeiwen.com/subject/zmkuiktx.html