概述
之前有写过springboot缓存应用的说明(《springboot入门01 – 缓存的使用》)。不过实际的场景有时候会比较复杂一些,比如:需要同时使用redis和caffine来做多级缓存,或者需要在通用配置外应用一些个性化的配置。使用多个CacheManager
来分别管理不同的缓存是应对这种问题的一个常规方案。
接下来介绍下如何实现多CacheManager
应用。
多CacheManager
应用
这里会通过一个具体的案例来进行演示。需求大致是这样的:在应用中的大部分场景都使通用的缓存配置,但是部分特殊场景需要做个性化的配置。在接下来演示中我们主要会用到Caffeine缓存。
配置
先修改下配置。这次如果继续使用SpringBoot的自启动CacheManager
会有一些不太好控制的地方,因此不宜再使用默认的缓存配置,需要做些独立配置:
1 2 3 4 |
caching: spec: initialCapacity=1048576,maximumSize=1073741824,expireAfterAccess=10m special: worker: initialCapacity=32,maximumSize=128,expireAfterWrite=30s |
其中 caching.spec 表示通用的缓存配置, caching.special则表示一组需要做个性化配置的特例。
通用配置是一行字符串,读取的时候可以直接使用@Value
注解,个性化配置则是一个Map结构,读取的时候需要用到@ConfigurationProperties
注解,大致如下:
1 2 3 4 5 6 7 8 |
@Value("${caching.spec}") private String commonSpec; @Bean @ConfigurationProperties("caching.special") public Map<String, String> getSpecialCase() { return new HashMap<>(4); } |
(关于配置文件读取可以参考之前的一篇旧文:《springboot入门07 – 配置文件详解》)
创建CacheManager
接下来就是根据配置文件创建CacheManager
了。
创建通用CacheManager
直接使用CaffeineCacheManager
就可以了:
1 2 3 4 5 6 7 8 9 |
@Bean @Primary public CacheManager cacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); if (isNotBlank(this.commonSpec)) { cacheManager.setCacheSpecification(this.commonSpec); } return cacheManager; } |
注意@Primary
注解,这个注解表示没有特意注明时,优先选择这个CacheManager
。
管理个例的CacheManager
略有些麻烦,这里使用了SimpleCacheManager
,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@Bean("special") public CacheManager specialCacheManager(Map<String, String> cases) { SimpleCacheManager manager = new SimpleCacheManager(); if (cases != null) { List<CaffeineCache> 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<Object, Object> caffeineBuilder = Caffeine.from(spec); return new CaffeineCache(name, caffeineBuilder.build()); } |
在使用这个CacheManager
时还需要记得下在@CacheConfig
或@Cacheable
注解中注明对应的qualifier:
1 |
@CacheConfig(cacheNames = "worker", cacheManager = "special") |
至此,多缓存配置已经是没有问题了。详细代码可以参考Git: zhyea / multi-cache
这里区分通用CacheManager
与个例CacheManager
主要依赖@Primary
注解实现。接下来会介绍一些其他的方案。
继承CachingConfigurerSupport
继承抽象类CachingConfigurerSupport
后,可以通过实现(重写)cacheManager()
方法指明默认的CacheManager
。嗯,也就是说,节省了一个@Bean
和一个@Primary
注解。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
@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<String, String> 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<String, String> cases) { SimpleCacheManager manager = new SimpleCacheManager(); if (cases != null) { List<CaffeineCache> 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<Object, Object> caffeineBuilder = Caffeine.from(spec); return new CaffeineCache(name, caffeineBuilder.build()); } } |
管理个例的CacheManager
还是需要@Bean
注解并设置Qualifier的。
实现CacheResolver
这个方案,怎么说呢,应该是可操作性最强大的。如果场景再复杂些,完全可以考虑用这个方案来处理。但是就我们当前这个case来说,用CacheResolver
来实现应该是最繁琐的了。太繁琐了,懒得写了。
姑且写一个简单的例子来演示下CacheResolver
是怎么发挥作用的吧。
CacheResolver
的实现类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
@Component("multi") public class MultiCacheResolver implements CacheResolver, InitializingBean { @Value("${caching.spec}") private String commonSpec; private CacheManager manager; @Override public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) { Collection<Cache> caches = new LinkedList<>(); Set<String> 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
,像这样:
1 |
@CacheConfig(cacheNames = "worker", cacheResolver = "multi") |
就这样了。示例代码都放在了这里:zhyea / multi-cache
发表评论