JAVA中Ehcache的各种项目集成与使用初体验
8月28日 斩情道投稿 大家好,又见面了。
本文是笔者作为掘金技术社区签约作者的身份输出的缓存专栏系列内容,将会通过系列专题,讲清楚缓存的方方面面。如果感兴趣,欢迎关注以获取后续更新。
在上一篇文章《JAVA中使用最广泛的本地缓存?Ehcache的自信从何而来感受来自Ehcache的强大实力》中,介绍了Ehcache所具有的核心优秀特性,如数据持久化、多级缓存、集群能力等等。所谓纸上得来终觉浅、绝知此事要躬行,接下来我们就一起动手实践下,在项目中集成Ehcache并体验Ehcache的各种常见用法。
Ehcache的依赖集成与配置依赖引入
集成使用Ehcache的第一步,就是要引入对应的依赖包。对于Maven项目而言,可以在pom。xml中添加对应依赖:dependencygroupIdorg。ehcachegroupIdehcacheartifactIdversion3。10。0versiondependency
依赖添加完成后,还需要对缓存进行配置后方可使用。
缓存的配置与创建使用代码配置与创建Ehcache
Ehcache支持在代码中手动创建缓存对象,并指定对应缓存参数信息。在使用之前,需要先了解几个关键代码类:
类名
具体说明
CacheManagerBuilder
CacheManager对象的构造器对象,可以方便的指定相关参数然后创建出符合条件的CacheManager对象。
ResourcePoolsBuilder
用于指定缓存的存储形式(ResourcePools)的配置构造器对象,可以指定缓存是堆内缓存、堆外缓存、磁盘缓存或者多者的组合,以及各个类型缓存的容量信息、是否持久化等信息。
CacheConfiguration
用于承载所有指定的关于缓存的配置属性值。
CacheConfigurationBuilder
用于生成最终缓存总体配置信息的构造器,可以指定缓存存储形式(ResourcePools)、过期策略(ExpiryPolicy)、键值类型等等各种属性值。
通过组合使用上述Builder构造器,我们便可以在代码中完成对缓存Cache属性的设置。比如下面这样:publicstaticvoidmain(String〔〕args){CacheManagercacheManagerCacheManagerBuilder。newCacheManagerBuilder()。with(CacheManagerBuilder。persistence(d:myCache))。build(true);指定缓存的存储形式,采用多级缓存,并开启缓存持久化操作ResourcePoolsresourcePoolsResourcePoolsBuilder。newResourcePoolsBuilder()。heap(1,MemoryUnit。MB)。disk(10,MemoryUnit。GB,true)。build();封装缓存配置对象,指定了键值类型、指定了使用TTL与TTI联合的过期淘汰策略CacheConfigurationInteger,StringcacheConfigurationCacheConfigurationBuilder。newCacheConfigurationBuilder(Integer。class,String。class,resourcePools)。withExpiry(ExpiryPolicyBuilder。timeToIdleExpiration(Duration。ofSeconds(10)))。withExpiry(ExpiryPolicyBuilder。timeToLiveExpiration(Duration。ofSeconds(5)))。build();使用给定的配置参数,创建指定名称的缓存对象CacheInteger,StringmyCachecacheManager。createCache(myCache,cacheConfiguration);}
上面的示例中,我们创建了一个基于heapdisk的二级缓存对象,并开启了缓存的持久化,以及指定了持久化结果文件的存储路径。
基于XML配置Ehcache
因为Ehcache在创建缓存的时候可以指定的参数较多,如果通过上面的代码方式指定配置,略显繁琐且不够清晰直观,并且当需要创建多个不同的缓存对象的时候比较麻烦。好在Ehcache还提供了一种通过XML来进行参数配置的途径,并且支持在一个xml中配置多个不同的缓存对象信息。
在项目的resource目录下添加个Ehcache的配置文件,比如取名ehcache。xml,项目层级结构示意如下:
然后我们在ehcache。xml中添加配置内容。内容示例如下:?xmlversion1。0encodingUTF8?configxmlns:xsihttp:www。w3。org2001XMLSchemainstancexmlns:jsr107http:www。ehcache。orgv3jsr107xmlnshttp:www。ehcache。orgv3xsi:schemaLocationhttp:www。ehcache。orgv3http:www。ehcache。orgschemaehcachecore3。1。xsdhttp:www。ehcache。orgv3jsr107http:www。ehcache。orgschemaehcache107ext3。1。xsdpersistencedirectoryD:myCachecachealiasmyCachekeytypejava。lang。Integerkeytypevaluetypejava。lang。Stringvaluetypeexpiryttiunitminutes5ttiexpiryresourcesheapunitMB10heapoffheapunitMB50offheapdiskpersistenttrueunitMB500diskresourcescacheconfig
上面演示的Ehcache3。x版本中的配置实现方式(配置文件与Ehcache2。x存在较大差异,不要混用,运行会报错),在xml中指定了myCache的key与value对应的类型,指定了基于TTI的5分钟过期淘汰策略,并规定了采用heapoffheapdisk的三级缓存机制,此外还开启了缓存持久化能力,并指定了持久化文件的存储路径。
通过xml配置的方式,可以很直观的看出这个缓存对象的所有关键属性约束,也是相比于代码中直接配置的方式更有优势的一个地方。在xml配置文件中,也可以同时配置多个缓存对象信息。此外,为了简化配置,Ehcache还支持通过来将一些公用的配置信息抽取出来成为模板,然后各个Cache独立配置的时候只需要增量配置各自差异化的部分即可,当然也可以基于给定的模板进行个性化的修改覆写配置。
比如下面这个配置文件,配置了两个Cache对象信息,复用了同一个配置模板,然后各自针对模板中不符合自己的配置进行了重新改写。?xmlversion1。0encodingUTF8?configxmlns:xsihttp:www。w3。org2001XMLSchemainstancexmlns:jsr107http:www。ehcache。orgv3jsr107xmlnshttp:www。ehcache。orgv3xsi:schemaLocationhttp:www。ehcache。orgv3http:www。ehcache。orgschemaehcachecore3。1。xsdhttp:www。ehcache。orgv3jsr107http:www。ehcache。orgschemaehcache107ext3。1。xsdpersistencedirectoryD:myCachecachetemplatenamemyTemplatekeytypejava。lang。Stringkeytypevaluetypejava。lang。Stringvaluetypeexpiryttlunitminutes30ttlexpiryresourcesheapunitMB10heapdiskunitGBpersistenttrue2diskresourcescachetemplatecachealiasmyCacheusestemplatemyTemplatekeytypejava。lang。IntegerkeytypecachecachealiasmyCache2usestemplatemyTemplateexpiryttlunitminutes60ttlexpirycacheconfig
配置完成之后,我们还需要在代码中指定使用此配置文件进行CacheManager创建与配置,并且完成CacheManager的init初始化操作。publicCacheInteger,StringcreateCacheWithXml(){获取配置文件URLxmlConfigUrlthis。getClass()。getClassLoader()。getResource(。ehcache。xml);解析对应的配置文件并创建CacheManager对象XmlConfigurationxmlConfigurationnewXmlConfiguration(xmlConfigUrl);CacheManagercacheManagerCacheManagerBuilder。newCacheManager(xmlConfiguration);执行初始化操作cacheManager。init();直接从CacheManager中根据名称获取对应的缓存对象returncacheManager。getCache(myCache,Integer。class,String。class);}
这样,Ehcache的集成与配置就算完成了,接下来直接获取Cache对象并对其进行操作即可。publicstaticvoidmain(String〔〕args){EhcacheServiceehcacheServicenewEhcacheService();CacheInteger,StringcacheehcacheService。createCacheWithXml();cache。put(1,value1);System。out。println(cache。get(1));}
当然,Ehcache3。x版本中使用xml方式配置的时候,有几个坑需要提防,避免踩坑。对于过期时间的设定只允许选择ttl或者tti中的一者,不允许两者同时存在而通过代码配置的时候则没有这个问题。如果在xml中同时指定ttl与tti则运行的时候会抛异常。
节点下面配置的时候,节点需要放在节点的前面,否则会报错Schema校验失败。
业务中使用
缓存设置并创建完成后,业务代码中便可以通过Ehcache提供的接口,进行缓存数据的相关操作。业务使用是通过对Cache对象的操作来进行的,Cache提供的API接口与JDK中的Map接口极其相似,所以在使用上毫无门槛,可以直接上手。
实际编码中,根据业务的实际诉求,通过Cache提供的API接口来完成缓存数据的增删改查操作。publicstaticvoidmain(String〔〕args){EhcacheServiceehcacheServicenewEhcacheService();CacheInteger,StringcacheehcacheService。getCache();存入单条记录到缓存中cache。put(1,value1);MapInteger,StringvaluesnewHashMap();values。put(2,value2);values。put(3,value3);批量向缓存中写入数据cache。putAll(values);当缓存不存在的时候才写入缓存cache。putIfAbsent(2,value2);查询单条记录System。out。println(cache。get(2));批量查询操作System。out。println(cache。getAll(Stream。of(1,2,3)。collect(Collectors。toSet())));移除单条记录cache。remove(1);System。out。println(cache。get(1));清空缓存记录cache。clear();System。out。println(cache。get(1));}
从上述代码可以看出,EhCache具体使用起来与普通Map操作无异。虽然使用简单,但是这样也存在个问题就是业务代码所有使用缓存的地方,都需要强依赖Ehcache的具体接口,导致业务代码与Ehcache的依赖耦合度太高,后续如果想要更换缓存组件时,难度会非常大。
在前面的文章《聊一聊JAVA中的缓存规范虽迟但到的JCacheAPI与天生不俗的SpringCache》中有介绍过JAVA业界的缓存标准规范,主要有JSR107标准与SpringCache标准,如果可以通过标准的接口方式进行访问,这样就可以解决与EhCache深度耦合的问题了。令人欣慰的是,Ehcache同时提供了对JSR107与SpringCache规范的支持!
下面一起看下如何通过JSR107规范接口以及SpringCache的标准来使用Ehcache。
通过JCacheAPI来使用Ehcache依赖集成与配置
如果要使用JCache标准方式来使用,需要额外引入JCache对应依赖包:dependencygroupIdjavax。cachegroupIdcacheapiartifactIdversion1。1。1versiondependency
按照JCache的规范,必须通过CacheManager才能获取到Cache对象(这一点与Ehcache相同),而CacheManager则又需要通过CacheProvider来获取。
遵循这一原则,我们可以按照JCache的方式来得到Cache对象:importjavax。cache。Cimportjavax。cache。CacheMimportjavax。cache。Cimportjavax。cache。configuration。MutableCimportjavax。cache。expiry。CreatedExpiryPimportjavax。cache。expiry。Dimportjavax。cache。spi。CachingPpublicclassJsrCacheService{publicCacheInteger,StringgetCache(){CachingProvidercachingProviderCaching。getCachingProvide();CacheManagercacheManagercachingProvider。getCacheManager();MutableConfigurationInteger,StringconfigurationnewMutableConfigurationInteger,String()。setTypes(Integer。class,String。class)。setStoreByValue(false)。setExpiryPolicyFactory(CreatedExpiryPolicy。factoryOf(Duration。ONEMINUTE));CacheInteger,StringmyCachecacheManager。createCach(myCache,configuration);System。out。println(myCache。getClass()。getCanonicalName());returnmyC}}
从import的内容可以看出上述代码没有调用到任何Ehcache的类,调用上述代码执行并打印出构建出来的Cache对象具体类型如下,可以看出的的确确创建出来的是Ehcache提供的Eh107Cache类:org。ehcache。jsr107。Eh107Cache
这是为什么呢?其实原理很简单,之前介绍JCacheAPI的文章中也有解释过。JCache中的CacheProvider其实是一个SPI接口,Ehcache实现并向JVM中注册了这一接口,所以JVM可以直接加载使用了Ehcache提供的实际能力。翻看下Ehcache的源码,我们也可以找到其SPI注册对应的配置信息:
这里还有一个需要注意的点,因为SPI接口有可能被多个组件实现,而且可能会有多个组件同时往JVM中注册了javax。cache。spi。CachingProvider这一SPI接口的实现类,这种情况下,上述代码执行的时候会报错,因为没有指定具体使用哪一个SPI,所以JVM出现了选择困难症,只能抛异常了:
所以为了避免这种情况的发生,我们可以在获取CacheProvider的时候,指定加载使用Ehcache提供的具体实现类org。ehcache。jsr107。EhcacheCachingProvider即可。CachingProvidercachingProviderCaching。getCachingProvider(org。ehcache。jsr107。EhcacheCachingProvider);
上面代码中,使用了JCache的MutableConfiguration类来实现缓存配置的设定。作为通用规范,JCache仅定义了所有缓存实现者需要实现的功能的最小集,而Ehcache除了JCache提供的最低限度缓存功能外,还有很多其余缓存不具备的增强特性。如果需要使用这些特性,则需要使用Ehcache自己的缓存配置类来实现。
举个例子,MutableConfiguration只能设定基于内存缓存的一些行为参数,而如果需要配置Ehcache提供的heapoffheapdisk三级缓存能力,或者是要开启Ehcache的持久化能力,则MutableConfiguration就有点爱莫能助,只能Ehcache亲自出马了。
比如下面这样:publicCacheInteger,StringgetCache(){CacheConfigurationInteger,StringcacheConfigurationCacheConfigurationBuilder。newCacheConfigurationBuilder(Integer。class,String。class,ResourcePoolsBuilder。heap(10)。offheap(20,MemoryUnit。MB))。build();EhcacheCachingProvidercachingProvider(EhcacheCachingProvider)Caching。getCachingProvider();CacheManagercacheManagercachingProvider。getCacheManager();returncacheManager。createCache(myCache,Eh107Configuration。fromEhcacheCacheConfiguration(cacheConfiguration));}
当然,也可以在JCache中继续使用Ehcache的xml配置方式。如下示意:publicCacheInteger,StringgetCache3()throwsURISyntaxException{CachingProvidercachingProviderCaching。getCachingProvider();CacheManagermanagercachingProvider。getCacheManager(getClass()。getClassLoader()。getResource(。ehcache。xml)。toURI(),getClass()。getClassLoader());returnmanager。getCache(myCache,Integer。class,String。class);}
相比于使用纯粹的JCacheAPI方式,上述两种使用Ehcache自己配置的方式可以享受到Ehcache提供的一些高级特性。但代价就是业务代码与Ehcache的解耦不是那么彻底,好在这些依赖仅在创建缓存的地方,对整体代码的耦合度影响不是很高,属于可接受的范围。
业务中使用
完成了通过JCacheAPI获取Cache对象,然后业务层代码中,便可以基于Cache对象提供的一系列方法,对缓存的具体内容进行操作了。publicstaticvoidmain(String〔〕args)throwsException{JsrCacheServiceservicenewJsrCacheService();CacheInteger,Stringcacheservice。getCache();cache。put(1,value1);cache。put(2,value2);System。out。println(cache。get(1));cache。remove(1);System。out。println(cache。containsKey(1));}
在Spring中集成Ehcache
作为JAVA领域霸主级别的存在,Spring凭借其优良的设计与出色的表现俘获了大批开发人员青睐,大部分项目都使用Spring作为基础框架来简化编码逻辑。Ehcache可以整合到Spring中,并搭配SpringCache的标准化注解,让代码可以以一种更加优雅的方式来实现缓存的操作。依赖集成与配置
以SpringBoot项目为例进行说明,首先需要引入对应的依赖包。对于maven项目,在pom。xml中添加如下配置:dependencygroupIdorg。springframework。bootgroupIdspringbootstartercacheartifactIddependencydependencygroupIdorg。ehcachegroupIdehcacheartifactIddependency
依赖引入之后,我们需要在配置文件中指定使用Ehcache作为集成的缓存能力提供者,并且可以指定ehcache。xml独立的配置文件(ehcache。xml配置文件需要放置在resource目录下):spring。cache。typeehcachespring。cache。ehcache。config。ehcache。xml
然后我们需要在项目启动类上添加上EnableCaching来声明开启缓存能力:SpringBootApplicationEnableCachingpublicclassCrawlerApplication{。。。}
到这里,对于Ehcache2。x版本而言,就已经完成集成预配置操作,可以直接在代码中进行操作与使用了。但是对于Ehcache3。x版本而言,由于Spring并未提供对应的CacheManager对其进行支持,如果这个时候我们直接启动程序,会在启动的时候就被无情的泼上一盆冷水:
为了实现Ehcache3。x与Spring的集成,解决上述的问题,需要做一些额外的适配逻辑。根据报错信息,首先可以想到的就是手动实现cacheManager的创建与初始化。而由于SpringCache提供了对JSR107规范的支持,且Ehcache3。x也全面符合JSR107规范,所以我们可以将三者结合起来,以JSR107规范作为桥梁,实现SpringBoot与Ehcache3。x的集成。
这个方案也即目前比较常用的SpringBootJCacheEhcache组合模式。首先需要在前面已有实现的基础上,额外增加对JCache的依赖:dependencygroupIdjavax。cachegroupIdcacheapiartifactIdversion1。1。1versiondependency
其次,需要修改下application。properties配置文件,将SpringCache声明使用的缓存类型改为JCache。spring。cache。typejcachespring。cache。jcache。config。ehcache。xml
上面的配置看着略显魔幻,也是很多不清楚原有的小伙伴们会比较疑惑的地方(我曾经刚在项目中看到这种写法的时候,就一度怀疑是别人代码配置写错了)。但是经过上述的原因阐述,应该就明白其中的寓意了。
接下来,需要在项目中手动指定使用ehcache。xml配置文件来构建cacheManager对象。ConfigurationpublicclassEhcacheConfig{BeanpublicJCacheManagerFactoryBeancacheManagerFactoryBean()throwsException{JCacheManagerFactoryBeanfactoryBeannewJCacheManagerFactoryBean();factoryBean。setCacheManagerUri(getClass()。getClassLoader()。getResource(ehcache。xml)。toURI());returnfactoryB}BeanpublicCacheManagercacheManager(javax。cache。CacheManagercacheManager){JCacheCacheManagercacheCacheManagernewJCacheCacheManager();cacheCacheManager。setCacheManager(cacheManager);returncacheCacheM}}
这样,就完成了通过JCache桥接来实现Spring中使用Ehcache3。x版本的目的了。
支持SpringCache注解操作
完成了Spring与Ehcache的整合之后,便可以使用SpringCache提供的标准注解来实现对Ehcache缓存的操作。
首先需了解SpringCache几个常用的注解及其含义:
注解
含义说明
EnableCaching
开启使用缓存能力
Cacheable
添加相关内容到缓存中
CachePut
更新相关缓存记录
CacheEvict
删除指定的缓存记录,如果需要清空指定容器的全部缓存记录,可以指定allEntitiestrue来实现
通过注解的方式,可以轻松的实现将某个方法调用的入参与响应映射自动缓存起来,基于AOP机制,实现了对业务逻辑无侵入式的静默缓存处理。ServiceSlf4jpublicclassTestService{Cacheable(cacheNamesmyCache,keyid)publicStringqueryById(intid){log。info(queryById方法被执行);}CachePut(cacheNamesmyCache,keyid)publicStringupdateIdValue(intid,StringnewValue){log。info(updateIdValue方法被执行);returnnewV}CacheEvict(cacheNamesmyCache,keyid)publicvoiddeleteById(intid){log。info(deleteById方法被执行);}}
通过注解的方式指定了各个方法需要配套执行的缓存操作,具体业务代码里面则聚焦于自身逻辑,无需操心缓存的具体实现。可以通过下面的代码测试下集成后的效果:GetMapping(test)publicStringtest(){StringvaluetestService。queryById(123);System。out。println(第一次查询,结果:value);valuetestService。queryById(123);System。out。println(第二次查询,结果:value);testService。updateIdValue(123,newValue123);valuetestService。queryById(123);System。out。println(更新后重新查询,结果:value);testService。deleteById(123);valuetestService。queryById(123);System。out。println(删除后重新查询,结果:value);returnOK;}
执行结果如下:queryById方法被执行第一次查询,结果:value123第二次查询,结果:value123updateIdValue方法被执行更新后重新查询,结果:newValue123deleteById方法被执行queryById方法被执行删除后重新查询,结果:newValue123
从测试结果可以看出,查询之后方法的入参与返回值被做了缓存,再次去查询的时候并没有真正的执行具体的查询操作方法,而调用删除方法之后再次查询,又会触发了真正的查询方法的执行。
小结回顾
好啦,关于Ehcache的各种配置、以及通过JSR107或者SpringCache规范集成到项目中使用的相关内容,就介绍到这里了。不知道小伙伴们是否对Ehcache的使用有了进一步的了解呢?而关于Ehcache,你是否有自己的一些想法与见解呢?欢迎评论区一起交流下,期待和各位小伙伴们一起切磋、共同成长。
补充说明1:本文属于《深入理解缓存原理与实战设计》系列专栏的内容之一。该专栏围绕缓存这个宏大命题进行展开阐述,全方位、系统性地深度剖析各种缓存实现策略与原理、以及缓存的各种用法、各种问题应对策略,并一起探讨下缓存设计的哲学。
如果有兴趣,也欢迎关注此专栏。
补充说明2:关于本文中涉及的演示代码的完整示例,我已经整理并提交到github中,如果您有需要,可以自取:https:github。comveezeanJavaBasicSkills
我是悟道,聊技术、又不仅仅聊技术
如果觉得有用,请点赞关注让我感受到您的支持。也可以关注下我的公众号【架构悟道】,获取更及时的更新。
期待与你一起探讨,一起成长为更好的自己。
投诉 评论
巴西新老名将先后负伤,球王贝利生命垂危,巴西能否挺过难关?2022世界杯在卡塔尔世界杯小组赛的最后一轮。五星巴西对阵喀麦隆。这届巴西可以说是夺冠最大的热门,因为球队里面众星云集。没人会认为巴西这场会输,尽管他已经确认出现,……
个月宝宝的食谱推举个月宝宝的食谱推举(个月宝宝的食谱推举)辅食推举:个月个月的宝宝饮食仍以母乳(或配方奶)为主,辅食添加以尝试吃为重要目标。添加的量从少量开始,即从~勺开始,以后逐步……
为什么越来越多小米老用户,在换下一部手机时,再不考虑小米手机众所周知,小米凭借着性价比的名头一跃成为大众所喜爱的手机品牌,但是最近几年,随着手机行业的发展,越来越多的小米老用户,再换下一部手机时,再也不选择小米了,这是为什么呢,小编找了……
两情相悦是心安情感点评大赏文王民官有朋友在谈到婚姻家庭生活是否幸福时,不无感慨地说,爱上一个让你心安的人,才是人生最大的福分!有这么经典的一段话:世间的一切,都是遇见。冷遇……
真皮沙发和布艺沙发哪个环保国家,标准规定每千克织物或皮革、行业标准中,甲醛含量不能超过300毫克,皮革沙发和布艺沙发均属于c类。而针对真皮沙发等皮质家具,目前国家还未制定相应的检测标准,仍属空白,事实上……
鄱阳湖河床变草原,当地人开车飞驰近ri,受持续高温天气影响,鄱阳湖水位持续下降,面积也直接缩小了34。现在鄱阳湖湖底,已经形成了一摊摊的水洼长出来大片的绿茶。长出来的草也很茂盛,大概有二三十厘米高,面积……
花样馒头清明期间,我跟爸爸一起学做馒头。我们去超市买了一袋面粉,一到家我就兴奋地拿起脸盆,要把面粉往脸盆里装。爸爸说:这可不行,要把手洗干净再倒下去。我一洗完手就迫不及待地把手扑……
孤傲的女人把爱情当赌博生活对你关闭一扇门的时候,必然暗中为你开启了另一扇,关键在于,你是否能够找到它?约访人:阿芬,女,26岁,公司白领。阿芬长得不算漂亮,但衣着得体,化的妆也恰到好处,……
花好月圆的日子喊你坐下来一起吃个饭赏月,吃月饼中秋节应该抬头看看月亮,在外地的你今天回家吗?为什么任何节日都会想到小时候,那时一大家子围在桌子上吃饭、哈哈大笑。。。。。。小孩子们还……
客厅装水晶灯好不好教你怎么选购水晶灯客厅装水晶灯好不好首先从材质和风水方面来讲,客厅安装水晶灯是不错的,因为水晶灯是水晶材料制成,而水晶为风水石,因为天然水晶凝聚了亿万年天地之间的灵气,每一种水晶均代表着不……
施舍与祈求的婚姻作者:红树林高级心理咨询师陈岫在婚姻中,你到底在施舍还是在祈求,亦或是既施又求。如果你觉得很难理解对方,那也有可能是因为你也无法理解自己。人生最大的一个问题就是:看……
男女造句用男女造句大全121、在诸子百家中,道家对男女之道的研究是最令人看好的,这不是因为道家的学说比其它的学说更有道理,而是道家的男女之道最讲实用性,所以也就被社会广泛的接受。122、在封建……