Spring Boot 如何选择 Cache 实现的
source link: https://yanbin.blog/how-springboot-select-cache-implementation/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Spring Boot 如何选择 Cache 实现的
2022-08-15 — Yanbin写作此篇是作为对 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 类中,我们看到
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 中的枚举值定义
public enum CacheType { GENERIC, JCACHE, HAZELCAST, COUCHBASE, REDIS, CACHE2K, CAFFEINE, SIMPLE, |
我们目前所能选择的 Cache 实现只能出自于上面几种,当然通过定义 Cache(GenericCacheCofiguration)
或 CacheManager(SimpleCacheConfiguration)
的方式就能无限可能了。
注意, CacheType 枚举值的定义也决定了 Spring 引入相应的 XXXCacheConfiguration 类的顺序,这对选择使用哪种 Cache 是有影响的。
默认选择 Cache
当我们在一个干净的 Spring 环境中,以上的 #2 - #5 的条件都未满足的情况下,SpringBoot 将选择 SimpleCacheConfiguration
, 而不是 GenericCacheConfiguration
. 原因是这两个 SpringBean 依赖的条件不同
SimpleCacheConfiguration
@Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(CacheManager.class) @Conditional(CacheCondition.class) class SimpleCacheConfiguration { ...... |
GenericCacheConfiguration
@Configuration(proxyBeanMethods = false) @ConditionalOnBean(Cache.class) @ConditionalOnMissingBean(CacheManager.class) @Conditional(CacheCondition.class) class GenericCacheConfiguration { ...... |
因为 Cache
Bean 也不存在,所以选择的是 SimpleCacheConfiguration
。
再比较一下 NonOpCacheConfiguration
@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
@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
@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 类的声明
@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 中要具备
<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
<ehcache> <cache name="users" maxEntriesLocalHeap="10000"/> </ehcache> |
这是一个最简陋但能工作的配置文件
现在使用 @Cacheable 等注解将使用 Ehcache 缓存。
其他的 Cache 实现也类似,再比如 CaffeienCacheConfiguration
@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)
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK