写作此篇是作为对 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
- 不要声明 CacheManagerBean
- 不要配置 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)
