Ehcache2.x/Ehcache3.x和Spring Cache整合
Ehcache2.x
和Ehcache3.x
它最大的一个特点是:3.x不向下兼容2.x。从他俩的GAV
坐标也能看出这种差异:
<!-- https://mvnrepository.com/artifact/net.sf.ehcache/ehcache --> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.10.6</version> </dependency> <!-- https://mvnrepository.com/artifact/org.ehcache/ehcache --> <dependency> <groupId>org.ehcache</groupId> <artifactId>ehcache</artifactId> <version>3.7.1</version> </dependency>
不仅仅GAV变了,包名也都换了,因此是二进制不兼容的,并且3.x和2.x的API都有非常大的差异。
虽然说2.x也还是维护着(毕竟有非常重的历史包袱),但是活跃度已经远不及3.x了,因此我认为拥抱EhCache3.x是大势所趋
这里有意思的是,spring-context-support即使在Spring5后,默认支持的还是EhCache2.x版本(毕竟有很重的历史包袱在呢),并且没有提供3.x版本的支持,这应该也是为何你看到大多数人还只是在使用EhCache2.x的根本原因吧~
Ehcache2.x集成
Ehcache2.x的集成方案几乎同Caffeine,略过。
2.x配置CacheManager的时候,既能全用API方式。当然也能简便的使用ehcache.xml方式,内容形如:
<?xml version="1.0" encoding="UTF-8"?> <ehcache> <diskStore path="d:/ehcache/"></diskStore> <!-- 默认缓存配置 --> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" /> <!-- User缓存配置 --> <cache name="User" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600" overflowToDisk="true"/> </ehcache>
关于xml配置文件的更多属性和含义,请参考官方文档的说明
Ehcache3.x集成
Ehcache3.x的社区比EhCache2.x活跃很多,所以拥抱和使用3.x版本似乎是必然的。但是奈何Spring并没有提供内置的CacheManager对3.x提供支持,因此此处我总结继承它的两种方案:
- 自己实现CacheManager和Cache等相关规范接口
- 使用JSR107的JCache(推荐)
上面截图我们能看到support包里是有对jcache(JSR107)的支持,而切好EhCache3.x它实现了JSR107规范(但没有实现Spring-Cache),为了集成它,我们就用现成的方案:jcache+EhCache3.x来实现对Spring的整合。
第一步:先导包
<!-- JSR107 --> <dependency> <groupId>javax.cache</groupId> <artifactId>cache-api</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>org.ehcache</groupId> <artifactId>ehcache</artifactId> <version>3.7.1</version> </dependency>
先简单看看jcache中JCacheCacheManager的实现:
// @since 3.2 public class JCacheCacheManager extends AbstractTransactionSupportingCacheManager { // 可见JCacheCacheManager其实就相当于代理,实际做事的是javax.cache.CacheManager @Nullable private CacheManager cacheManager; private boolean allowNullValues = true; public JCacheCacheManager() { } public JCacheCacheManager(CacheManager cacheManager) { this.cacheManager = cacheManager; } ... @Override public void afterPropertiesSet() { if (getCacheManager() == null) { setCacheManager(Caching.getCachingProvider().getCacheManager()); } super.afterPropertiesSet(); } // 它使用的是JCacheCache俩把javax.cache.Cache包装起来 类似于适配的效果 @Override protected Collection<Cache> loadCaches() { CacheManager cacheManager = getCacheManager(); Assert.state(cacheManager != null, "No CacheManager set"); Collection<Cache> caches = new LinkedHashSet<>(); for (String cacheName : cacheManager.getCacheNames()) { javax.cache.Cache<Object, Object> jcache = cacheManager.getCache(cacheName); caches.add(new JCacheCache(jcache, isAllowNullValues())); } return caches; } @Override protected Cache getMissingCache(String name) { CacheManager cacheManager = getCacheManager(); Assert.state(cacheManager != null, "No CacheManager set"); // Check the JCache cache again (in case the cache was added at runtime) javax.cache.Cache<Object, Object> jcache = cacheManager.getCache(name); if (jcache != null) { return new JCacheCache(jcache, isAllowNullValues()); } return null; } }
由此可见,实际上JCache就相当于对JSR107做了一层适配,让所有实现了JSR107的缓存方案,都能够用在Spring环境中。
第二步:准备配置(集成)方案,此处给出两种配置方案:
一、使用最容易的JCacheManagerFactoryBean + ehcache.xml的方式:
@EnableCaching @Configuration public class CacheConfig extends CachingConfigurerSupport { @Bean public JCacheManagerFactoryBean cacheManagerFactoryBean() throws IOException { JCacheManagerFactoryBean factoryBean = new JCacheManagerFactoryBean(); // 配置全部写在ehcache.xml这个配置文件内~~~~ factoryBean.setCacheManagerUri(new ClassPathResource("ehcache.xml").getURI()); return factoryBean; } @Bean public CacheManager cacheManager(javax.cache.CacheManager cacheManager) { // 它必须要包装一个javax.cache.CacheManager,也就是Eh107CacheManager才行 JCacheCacheManager cacheCacheManager = new JCacheCacheManager(); // 方式一:使用`JCacheManagerFactoryBean` + xml配置文件的方式 cacheCacheManager.setCacheManager(cacheManager); return cacheCacheManager; }
ehcache.xml配置文件如下:
<?xml version="1.0" encoding="UTF-8"?> <config xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns='http://www.ehcache.org/v3' xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.1.xsd"> <!-- <service> <jsr107:defaults> <jsr107:cache name="demoCache" template="heap-cache"/> </jsr107:defaults> </service> --> <cache-template name="heap-cache"> <resources> <heap unit="entries">2000</heap> <offheap unit="MB">100</offheap> </resources> </cache-template> <!-- 注意:这个cache必须手动配置上,它并不会动态生成 --> <cache alias="demoCache" uses-template="heap-cache"> <expiry> <ttl unit="seconds">40</ttl> </expiry> </cache> </config>
运行如上单侧,打印结果如下:
模拟去db查询~~~1 ----------验证缓存是否生效---------- org.springframework.cache.jcache.JCacheCache@1cc680e hello cache...
缓存生效(使用的JCacheCache)。
它的基本原理是依赖于Caching.getCachingProvider().getCacheManager()这句代码来生成CacheManager。而EhCache提供了EhcacheCachingProvider实现了CachingProvider接口从而实现了getCacheManager()方法~~~
二、使用org.ehcache.config.CacheConfiguration纯API的方式
略
关于进程缓存这一块,虽然有好几个产品可供选择,但我推荐使用caffeine。(有的小伙伴为了简单而使用EhCache2.x,我个人是不太推荐这种做法的)
总结
本文介绍了进程缓存之王Caffeine Cache和EhCache。互联网软件神速发展,用户的体验度是判断一个软件好坏的重要原因,所以缓存是必不可少的一个神器。
我曾经的曾经面试过一个一个小伙,让他说说对Spring缓存的理解,它一直描述Redis,从沟通细节中甚至一度让我觉得他眼中的Spring缓存就是指的Redis。希望本文能给小伙伴带来一些帮助,不要有这样的误以为,被同行知道了会很尴尬的~
最后我想说:使用分布式缓存Redis确实能应对非常多的场景(绝大部分都使用Redis这也造成了上面我描述的错觉),但真正意义上的优化、高速缓存等等都是必须对本地缓存有深入了解的~~