前言
本文在上篇文章的基础上做出修改,使其使用Ehcache.
Ehcache集成
在pom文件中加入如下配置:
<dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency>
在src/main/resources 目录下加入ehcache.xml.代码如下:
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd"> <!-- <diskStore>:当内存缓存中对象数量超过maxElementsInMemory时,将缓存对象写到磁盘缓存中(需对象实现序列化接口)。 --> <!-- <diskStore path="">:用来配置磁盘缓存使用的物理路径,Ehcache磁盘缓存使用的文件后缀名是*.data和*.index。 --> <diskStore path="java.io.tmpdir/ehcache" /> <!-- 默认缓存 --> <!-- maxElementsInMemory: 内存缓存中最多可以存放的元素数量,若放入Cache中的元素超过这个数值,则有以下两种情况。 1)若overflowToDisk=true,则会将Cache中多出的元素放入磁盘文件中。 2)若overflowToDisk=false,则根据memoryStoreEvictionPolicy策略替换Cache中原有的元素。 默认策略是LRU(最近最少使用) --> <!-- Eternal:缓存中对象是否永久有效,即是否永驻内存,true时将忽略timeToIdleSeconds和timeToLiveSeconds。 --> <!-- timeToIdleSeconds:缓存数据在失效前的允许闲置时间(单位:秒),仅当eternal=false时使用,默认值是0表示可闲置时间无穷大, 此为可选属性即访问这个cache中元素的最大间隔时间,若超过这个时间没有访问此Cache中的某个元素,那么此元素将被从Cache中清除。 --> <!-- timeToLiveSeconds:缓存数据在失效前的允许存活时间(单位:秒),仅当eternal=false时使用,默认值是0表示可存活时间无穷大, 即Cache中的某元素从创建到清楚的生存时间,也就是说从创建开始计时,当超过这个时间时,此元素将从Cache中清除。 --> <!-- overflowToDisk:内存不足时,是否启用磁盘缓存(即内存中对象数量达到maxElementsInMemory时,Ehcache会将对象写到磁盘中), 会根据标签中path值查找对应的属性值,写入磁盘的文件会放在path文件夹下,文件的名称是cache的名称,后缀名是data。 --> <defaultCache maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="false" /> <!-- 缓存名称,cache的唯一标识(ehcache会把这个cache放到HashMap里)。 --> <cache name="users" maxEntriesLocalHeap="200" timeToLiveSeconds="600"> </cache> </ehcache>
关于Ehcache的配置,可参考如下链接:
此时,我们就修改好了,这也能够体验到spring cache 的威力,不依赖于底层实现.
自动装配
EhCacheCacheManager的自动装配是在EhCacheCacheConfiguration中.
这里先提1个问题,spring boot 中默认支持的是ConcurrentMapCacheManager,现在我们再加入ehcache的依赖,不就有两个了吗? 这样有问题吗?
答案: 不会.原因如下:
chche的自动装配的总入口是在CacheAutoConfiguration中,该类通过@Import(CacheConfigurationImportSelector.class) 导入了CacheConfigurationImportSelector. 由于该类是ImportSelector的类型,因此会执行其selectImports方法,将其返回值接着调用ConfigurationClassParser#processImports处理.其selectImports代码如下:
public String[] selectImports(AnnotationMetadata importingClassMetadata) { CacheType[] types = CacheType.values(); String[] imports = new String[types.length]; for (int i = 0; i < types.length; i++) { imports[i] = CacheConfigurations.getConfigurationClass(types[i]); } return imports; }
遍历CacheType的枚举集合,依次获得其对应的配置类,加入到imports中 最后的返回值如下:
- GenericCacheConfiguration.class
- JCacheCacheConfiguration.class
- EhCacheCacheConfiguration.class
- HazelcastCacheConfiguration.class
- InfinispanCacheConfiguration.class
- CouchbaseCacheConfiguration
- RedisCacheConfiguration
- CaffeineCacheConfiguration
- GuavaCacheConfiguration
- SimpleCacheConfiguration
- NoOpCacheConfiguration
对于 GenericCacheConfiguration来说,其注解如下:
@Configuration @ConditionalOnBean(Cache.class) @ConditionalOnMissingBean(CacheManager.class) @Conditional(CacheCondition.class)
该类会生效.
对于 JCacheCacheConfiguration, HazelcastCacheConfiguration,… GuavaCacheConfiguration来说,都需要其相关的类在类路径下存在. 因此,在默认情况下在PARSE_CONFIGURATION阶段生效的配置有:
GenericCacheConfiguration, EhCacheCacheConfiguration, SimpleCacheConfiguration,NoOpCacheConfiguration
最后,执行ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass(ConfigurationClass, TrackedConditionEvaluator)时:
GenericCacheConfiguration: 由于@ConditionalOnBean(Cache.class)是在REGISTER_BEAN阶段时执行并且在beanFactory中不存在Cache类型的bean.因此该类不进行注册.证明:查询/autoconfig,其中有关GenericCacheConfiguration的结果如下:
GenericCacheConfiguration: { notMatched: [ { condition: "OnBeanCondition", message: "@ConditionalOnBean (types: org.springframework.cache.Cache; SearchStrategy: all) did not find any beans" } ], matched: [ { condition: "CacheCondition", message: "Cache org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration automatic cache type" } ] }
RedisCacheConfiguration:同样,不满足@ConditionalOnBean(RedisTemplate.class)的条件,因此不会执行注册.证明:查询/autoconfig,其中有关GenericCacheConfiguration的结果如下:
RedisCacheConfiguration: { notMatched: [ { condition: "OnBeanCondition", message: "@ConditionalOnBean (types: org.springframework.data.redis.core.RedisTemplate; SearchStrategy: all) did not find any beans" } ], matched: [ { condition: "CacheCondition", message: "Cache org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration automatic cache type" } ] }
SimpleCacheConfiguration–> 不满足@ConditionalOnMissingBean(CacheManager.class)条件(因为EhCacheCacheConfiguration中配置了),证明:
{ notMatched: [ { condition: "OnBeanCondition", message: "@ConditionalOnMissingBean (types: org.springframework.cache.CacheManager; SearchStrategy: all) found bean 'cacheManager'" } ], matched: [ { condition: "CacheCondition", message: "Cache org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration automatic cache type" } ] }
NoOpCacheConfiguration–> 不满足@ConditionalOnMissingBean(CacheManager.class)条件(因为EhCacheCacheConfiguration中配置了),证明:
NoOpCacheConfiguration: { notMatched: [ { condition: "OnBeanCondition", message: "@ConditionalOnMissingBean (types: org.springframework.cache.CacheManager; SearchStrategy: all) found bean 'cacheManager'" } ], matched: [ { condition: "CacheCondition", message: "Cache org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration automatic cache type" } ]
因此,最后生效的是EhCacheCacheManager
回到正题上来,EhCacheCacheConfiguration有如下注解:
@Configuration @ConditionalOnClass({ Cache.class, EhCacheCacheManager.class }) @ConditionalOnMissingBean(org.springframework.cache.CacheManager.class) @Conditional({ CacheCondition.class, EhCacheCacheConfiguration.ConfigAvailableCondition.class })
- @Configuration –> 配置类
- @ConditionalOnClass({ Cache.class, EhCacheCacheManager.class })–> 在当前类路径下存在 Cache.class, EhCacheCacheManager.class 时生效
- @ConditionalOnMissingBean(org.springframework.cache.CacheManager.class)–> beanFactory中不存在org.springframework.cache.CacheManager类型的bean时生效
@Conditional({ CacheCondition.class,
EhCacheCacheConfiguration.ConfigAvailableCondition.class })–>- CacheCondition –> 默认返回true,我们在上篇文章中分析过了
- EhCacheCacheConfiguration.ConfigAvailableCondition–> 如果配置有spring.cache.ehcache.config 则返回匹配,或者 如果在类路径下存在ehcache.xml则返回匹配.由于我们在类路径下创建了ehcache.xml,因此,返回匹配.
在该类中声明了2个@Bean方法:
ehCacheCacheManager,代码如下:
@Bean @ConditionalOnMissingBean public CacheManager ehCacheCacheManager() { Resource location = this.cacheProperties .resolveConfigLocation(this.cacheProperties.getEhcache().getConfig()); if (location != null) { return EhCacheManagerUtils.buildCacheManager(location); } return EhCacheManagerUtils.buildCacheManager(); }
- @Bean –> 注册1个id为ehCacheCacheManager,类型为CacheManager的bean
- @ConditionalOnMissingBean–> 当BeanFactory中不存在CacheManager类型的bean时生效
该方法的逻辑如下:
- 获得spring.cache.ehcache.config 配置的路径
- 如果配置了,则加载指定的路径创建CacheManager,否则,读取默认EhCache的文件路径:/ehcache.xml,/ehcache-failsafe.xml
由于我们没有配置spring.cache.ehcache.config,因此会读取ehcache.xml
cacheManager,代码如下:
@Bean public EhCacheCacheManager cacheManager(CacheManager ehCacheCacheManager) { return this.customizers.customize(new EhCacheCacheManager(ehCacheCacheManager)); }
- @Bean –> 注册1个id为cacheManager,类型为EhCacheCacheManager的bean
方法的逻辑如下:
- 实例化EhCacheCacheManager
- 根据CacheManagerCustomizers来做个性化配置.由于spring boot 没有装配CacheManagerCustomizer,因此该方法相当于空操作.