写作此篇是作为对 Spring 使用 Cache 解析及使用不同类型的 Cache 一文的补充,该文中提到了自定 CacheManager 及配置 spring.cache.type
来选择自己的 Cache 实例,但对 Spring 是如何确定具体 Cache 实现未作展开。本文将介绍选择 Cache 实现的几种方式
- 默认选择 Cache
- 声明 Spring Bean
Cache
- 声明 Spring Bean
CacheManager
- 通过
spring.cache.type
属性选择 - 引入相应的 Cache 实现依赖
首先来看 SpringBoot 所有支持的 Cache 实现,在 CacheConfigurations 类中,我们看到
1 2 3 4 5 6 7 8 9 10 11 12 13 |
static { Map<CacheType, String> mappings = new EnumMap<>(CacheType.class); mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class.getName()); mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class.getName()); mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class.getName()); mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class.getName()); mappings.put(CacheType.REDIS, RedisCacheConfiguration.class.getName()); mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class.getName()); mappings.put(CacheType.CACHE2K, Cache2kCacheConfiguration.class.getName()); mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class.getName()); mappings.put(CacheType.NONE, NoOpCacheConfiguration.class.getName()); MAPPINGS = Collections.unmodifiableMap(mappings); } |
或者是在 CacheType 中的枚举值定义
1 2 3 4 5 6 7 8 9 10 11 |
public enum CacheType { GENERIC, JCACHE, HAZELCAST, COUCHBASE, REDIS, CACHE2K, CAFFEINE, SIMPLE, NONE } |
我们目前所能选择的 Cache 实现只能出自于上面几种,当然通过定义 Cache(GenericCacheCofiguration)
或 CacheManager(SimpleCacheConfiguration)
的方式就能无限可能了。
注意, CacheType 枚举值的定义也决定了 Spring 引入相应的 XXXCacheConfiguration 类的顺序,这对选择使用哪种 Cache 是有影响的。
默认选择 Cache
当我们在一个干净的 Spring 环境中,以上的 #2 - #5 的条件都未满足的情况下,SpringBoot 将选择 SimpleCacheConfiguration
, 而不是 GenericCacheConfiguration
. 原因是这两个 SpringBean 依赖的条件不同
SimpleCacheConfiguration
1 2 3 4 5 6 |
@Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(CacheManager.class) @Conditional(CacheCondition.class) class SimpleCacheConfiguration { ...... } |
GenericCacheConfiguration
1 2 3 4 5 6 7 |
@Configuration(proxyBeanMethods = false) @ConditionalOnBean(Cache.class) @ConditionalOnMissingBean(CacheManager.class) @Conditional(CacheCondition.class) class GenericCacheConfiguration { ...... } |
因为 Cache
Bean 也不存在,所以选择的是 SimpleCacheConfiguration
。
再比较一下 NonOpCacheConfiguration
1 2 3 4 5 6 |
@Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(CacheManager.class) @Conditional(CacheCondition.class) class NoOpCacheConfiguration { ...... } |
它和 SimpleCacheConfiguration 有相同的 @ConditionalXxx 条件,那为什么不会默认为 NoOpCacheConfiguration
呢?
原因是在 CacheAutoConfiguration.CacheConfigurationIMportSelector.selectImports() 方法中先引入的是 SimpleCacheConfiguration,NoOpCacheConfiguration 是最后被引入的。 它在初始化 CacheManager 实例(ConcurrentMapCacheManager) 后,使得 NoOpCacheConfiguration
的 @OnditionalOnMissingBean(CacheManager.class)
不能满足,所以选择的是 SimpleCacheConfiguration
而其他的使用了第三方库的 Cache 必须依赖于是否引入了相应的库。
声明了 Cache
Bean 后选择 GenericCacheConfiguration
仍然要记记住 Spring 引入以下三种无第三方库依赖的 XxxCacheConfiguration 的顺序是
- GenericCacheConfiguration
- SimpleCacheConfiguration
- NoOpCacheConfiguration
它们都不想看到 CacheManager
实例,但 GenericCacheConfiguration 在存在 Cache
实例时被选择,所以只要声明一个自己的 Cache
验证很简单,在 Java config 中声明一个 Cache
1 2 3 4 5 6 7 8 |
@Configuration public class AppConfig { @Bean public Cache cache() { return new ConcurrentMapCache("xxx"); } } |
这样的话,使用 @Cacheable 等注解将会使用 GenericCacheConfiguration
实现
声明了 CacheManager
Bean 就直接用该 CacheManager
所有 XxxCacheConfiguration 都排斥 CacheManager
Bean, 所以只要声明了自己的 CacheManager
的话就很明确了,没有其他任何的 XxxCacheConfigruation 的事情了。
比如我们使用 Google Guava 的 Cache
1 2 3 4 5 6 7 8 9 10 11 12 |
@Configuration public class AppConfig { @Bean public CacheManager userCacheManager() { return new ConcurrentMapCacheManager("user"){ public Cache createConcurrentMapCache(String name){ return new ConcurrentMapCache(name, CacheBuilder.newBuilder() .expireAfterWrite(5, TimeUnit.MINUTES).maximumSize(1000).build().asMap(), false); } }; } } |
通过 spring.cache.type
选择 Cache
在既没有 Cache
和 CacheManager
Bean 实例定义的情况下,如果设置属性 spring.cache.type=None
, 将会选择 NoOpCacheConfiguration
中定义的 NoOpCacheManager
, 因为 spring.cache.type=None
将会产生下面的效果
- GenericCacheConfiguration: @Conditional(CacheCondition.class) match = false
- SimpleCacheConfiguration: @Conditional(CacheCondition.class) match = false
- NoOpCacheConfiguration: @Conditional(CacheCondition.class) match = true
唯有 NoOpCacheConfiguration 能满足初始化的条件
所以通过配置 spring.cache.type
可以强制选择所需的 XxxCacheConfiguration,以及其中的 CacheManager, 而不管在自己的应用中是否声明了自己 Cache 或 CacheManager,因为 spring.cache.type
直接否决掉不符合条件的 @Conditional(CacheCondition.class)。
启用第三方 Cache 实现
欲知如何启用第三方的 Cache 实现,那就直接看相应的 XxxCacheConfiguration 需满足的条件。其实对前面讲到的 GenericCacheConfiguration, SimpleCacheConfigruation, 和 NoOpCacheConfigruation 都是相同的道理。
比如要用 EhCache 的话,打开 EhcacheCacheConfiguration 类的声明
1 2 3 4 5 6 7 |
@Configuration(proxyBeanMethods = false) @ConditionalOnClass({ Cache.class, EhCacheCacheManager.class }) @ConditionalOnMissingBean(org.springframework.cache.CacheManager.class) @Conditional({ CacheCondition.class, EhCacheCacheConfiguration.ConfigAvailableCondition.class }) class EhCacheCacheConfiguration { ...... } |
必须满足的条件有
- 存在类
net.sf.ehcache.Cache
和org.springframework.cache.ehcache.EhCacheCacheManager
- 不要声明
CacheManager
Bean - 不要配置
spring.cache.type
,有 spring.cache.type 的话,值只能为 Ehcache(不区分大小写) - 要配置 Ehcache 的配置文件 classpath:/ehcache.xml
注:spring.cache.type
除了设置为 None
禁用 Cache 用,其实没什么用
因此我们要做的就是(假定为 Maven 项目)
引入依赖,在 pom.xml 中要具备
1 2 3 4 5 6 7 8 9 10 |
<dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.10.9.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>5.3.20</version> </dependency> |
添加 Ehcache 配置文件 src/main/resources/ehcache.xml
1 2 3 |
<ehcache> <cache name="users" maxEntriesLocalHeap="10000"/> </ehcache> |
这是一个最简陋但能工作的配置文件
现在使用 @Cacheable 等注解将使用 Ehcache 缓存。
其他的 Cache 实现也类似,再比如 CaffeienCacheConfiguration
1 2 3 4 5 6 7 |
@Configuration(proxyBeanMethods = false) @ConditionalOnClass({ Caffeine.class, CaffeineCacheManager.class }) @ConditionalOnMissingBean(CacheManager.class) @Conditional({ CacheCondition.class }) class CaffeineCacheConfiguration { ...... } |
要有 com.github.benmanes.caffeine.cache.Caffeine
类等条件。
如果不清楚 XxxCacheConfiguration 是否能被初始化, 可在 application.properties
中配置 debug=true
, 输出 CONDITIONS EVALUATION REPORT
的内容
EhCacheCacheConfiguration matched:
- @ConditionalOnClass found required classes 'net.sf.ehcache.Cache', 'org.springframework.cache.ehcache.EhCacheCacheManager' (OnClassCondition)
- Cache org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration EHCACHE cache type (CacheCondition)
- ResourceCondition (EhCache) found resource 'classpath:/ehcache.xml' (EhCacheCacheConfiguration.ConfigAvailableCondition)
- @ConditionalOnMissingBean (types: org.springframework.cache.CacheManager; SearchStrategy: all) did not find any beans (OnBeanCondition)CaffeineCacheConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'com.github.benmanes.caffeine.cache.Caffeine' (OnClassCondition)