文章目录
概述
之前有写过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
发表评论