springboot入门13 – 多CacheManager应用

概述

之前有写过springboot缓存应用的说明(《 springboot入门01 – 缓存的使用
》)。不过实际的场景有时候会比较复杂一些,比如:需要同时使用redis和caffine来做多级缓存,或者需要在通用配置外应用一些个性化的配置。使用多个

CacheManager


来分别管理不同的缓存是应对这种问题的一个常规方案。

接下来介绍下如何实现多

CacheManager


应用。




CacheManager


应用

这里会通过一个具体的案例来进行演示。需求大致是这样的:在应用中的大部分场景都使通用的缓存配置,但是部分特殊场景需要做个性化的配置。在接下来演示中我们主要会用到Caffeine缓存。

配置

先修改下配置。这次如果继续使用SpringBoot的自启动

CacheManager


会有一些不太好控制的地方,因此不宜再使用默认的缓存配置,需要做些独立配置:

caching:
  spec: initialCapacity=1048576,maximumSize=1073741824,expireAfterAccess=10m
  special:
    worker: initialCapacity=32,maximumSize=128,expireAfterWrite=30s

其中 caching.spec
表示通用的缓存配置, caching.special
则表示一组需要做个性化配置的特例。

通用配置是一行字符串,读取的时候可以直接使用

@
Value


注解,个性化配置则是一个 Map
结构,读取的时候需要用到

@
ConfigurationProperties


注解,大致如下:

    @Value("${caching.spec}")
    private String commonSpec;
 
    @Bean
    @ConfigurationProperties("caching.special")
    public Map getSpecialCase() {
        return new HashMap(4);
    }

(关于配置文件读取可以参考之前的一篇旧文:《 springboot入门07 – 配置文件详解
》)


创建

CacheManager



接下来就是根据配置文件创建

CacheManager


了。

创建通用

CacheManager


直接使用

CaffeineCacheManager


就可以了:

    @Bean
    @Primary
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        if (isNotBlank(this.commonSpec)) {
            cacheManager.setCacheSpecification(this.commonSpec);
        }
        return cacheManager;
    }

注意

@
Primary


注解,这个注解表示没有特意注明时,优先选择这个

CacheManager


管理个例的

CacheManager


略有些麻烦,这里使用了

SimpleCacheManager


,代码如下:

    @Bean("special")
    public CacheManager specialCacheManager(Map cases) {
        SimpleCacheManager manager = new SimpleCacheManager();
        if (cases != null) {
            List caches =
                    cases.entrySet().stream()
                            .map(e -> buildCaffeineCache(e.getKey(), e.getValue()))
                            .collect(Collectors.toList());
            manager.setCaches(caches);
        }
        return manager;
    }
 
    private CaffeineCache buildCaffeineCache(String name, String spec) {
        logger.info("Cache {} specified with config:{}", name, spec);
        final Caffeine caffeineBuilder = Caffeine.from(spec);
        return new CaffeineCache(name, caffeineBuilder.build());
    }

在使用这个

CacheManager


时还需要记得下在

@
CacheConfig




@
Cacheable


注解中注明对应的qualifier:

@CacheConfig(cacheNames = "worker", cacheManager = "special")

至此,多缓存配置已经是没有问题了。详细代码可以参考Git: zhyea / multi-cache

这里区分通用

CacheManager


与个例

CacheManager


主要依赖

@
Primary


注解实现。接下来会介绍一些其他的方案。


继承

CachingConfigurerSupport



继承抽象类

CachingConfigurerSupport


后,可以通过实现(重写)

cacheManager
(
)


方法指明默认的

CacheManager


。嗯,也就是说,节省了一个

@
Bean


和一个

@
Primary


注解。代码如下:

@Configuration
public class CachingConfigSupport extends CachingConfigurerSupport {
 
    private static final Logger logger = LoggerFactory.getLogger(CachingConfigSupport.class);
 
 
    @Value("${caching.spec}")
    private String commonSpec;
 
    @Bean
    @ConfigurationProperties("caching.special")
    public Map getSpecialCase() {
        return new HashMap(4);
    }
 
    @Override
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        if (isNotBlank(this.commonSpec)) {
            cacheManager.setCacheSpecification(this.commonSpec);
        }
        return cacheManager;
    }
 
 
    @Bean("special")
    public CacheManager specialCacheManager(Map cases) {
        SimpleCacheManager manager = new SimpleCacheManager();
        if (cases != null) {
            List caches =
                    cases.entrySet().stream()
                            .map(e -> buildCaffeineCache(e.getKey(), e.getValue()))
                            .collect(Collectors.toList());
            manager.setCaches(caches);
        }
        return manager;
    }
 
    private CaffeineCache buildCaffeineCache(String name, String spec) {
        logger.info("Cache {} specified with config:{}", name, spec);
        final Caffeine caffeineBuilder = Caffeine.from(spec);
        return new CaffeineCache(name, caffeineBuilder.build());
    }
}

管理个例的

CacheManager


还是需要

@
Bean


注解并设置Qualifier的。


实现

CacheResolver



这个方案,怎么说呢,应该是可操作性最强大的。如果场景再复杂些,完全可以考虑用这个方案来处理。但是就我们当前这个case来说,用

CacheResolver


来实现应该是最繁琐的了。太繁琐了,懒得写了。

姑且写一个简单的例子来演示下

CacheResolver


是怎么发挥作用的吧。



CacheResolver


的实现类如下:

@Component("multi")
public class MultiCacheResolver implements CacheResolver, InitializingBean {
 
    @Value("${caching.spec}")
    private String commonSpec;
 
    private CacheManager manager;
 
    @Override
    public Collection resolveCaches(CacheOperationInvocationContext context) {
        Collection caches = new LinkedList();
        Set cacheNames = context.getOperation().getCacheNames();
        for (String name : cacheNames) {
            caches.add(manager.getCache(name));
        }
        return caches;
    }
 
 
    public CacheManager newCacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        if (isNotBlank(this.commonSpec)) {
            cacheManager.setCacheSpecification(this.commonSpec);
        }
        return cacheManager;
    }
 
 
    @Override
    public void afterPropertiesSet() throws Exception {
        manager = newCacheManager();
    }
}

在代码中可以看到,是通过

resolveCaches
(
)


方法决定了提供哪些缓存。

使用

CacheResolver


后就有机会可以考虑不注入

CacheManager


的实例到容器中了,因为

CacheResolver


会管理会用到的

CacheManager


的实例。 不过在应用缓存注解的情况下,要记得指定使用哪个

CacheResolver


,像这样:

@CacheConfig(cacheNames = "worker", cacheResolver = "multi")

就这样了。示例代码都放在了这里: zhyea / multi-cache

参考文档