已阅读5页,还剩11页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Spring Cache抽象详解spring cahe缓存简介缓存,我的理解是:让数据更接近于使用者;工作机制是:先从缓存中读取数据,如果没有再从慢速设备上读取实际数据(数据也会存入缓存);缓存什么:那些经常读取且不经常修改的数据/那些昂贵(CPU/IO)的且对于相同的请求有相同的计算结果的数据。如CPU-L1/L2-内存-磁盘就是一个典型的例子,CPU需要数据时先从L1/L2中读取,如果没有到内存中找,如果还没有会到磁盘上找。还有如用过Maven的朋友都应该知道,我们找依赖的时候,先从本机仓库找,再从本地服务器仓库找,最后到远程仓库服务器找;还有如京东的物流为什么那么快?他们在各个地都有分仓库,如果该仓库有货物那么送货的速度是非常快的。缓存命中率即从缓存中读取数据的次数 与 总读取次数的比率,命中率越高越好:命中率 = 从缓存中读取次数 / (总读取次数从缓存中读取次数+ 从慢速设备上读取的次数)Miss率 = 没有从缓存中读取的次数/ (总读取次数从缓存中读取次数+ 从慢速设备上读取的次数)这是一个非常重要的监控指标,如果做缓存一定要健康这个指标来看缓存是否工作良好;缓存策略Eviction policy移除策略,即如果缓存满了,从缓存中移除数据的策略;常见的有LFU、LRU、FIFO:FIFO(First In First Out):先进先出算法,即先放入缓存的先被移除;LRU(Least Recently Used):最久未使用算法,使用时间距离现在最久的那个被移除;LFU(Least Frequently Used):最近最少使用算法,一定时间段内使用次数(频率)最少的那个被移除;TTL(Time To Live)存活期,即从缓存中创建时间点开始直到它到期的一个时间段(不管在这个时间段内有没有访问都将过期)TTI(Time To Idle)空闲期,即一个数据多久没被访问将从缓存中移除的时间。到此,基本了解了缓存的知识,在Java中,我们一般对调用方法进行缓存控制,比如我调用findUserById(Long id),那么我应该在调用这个方法之前先从缓存中查找有没有,如果没有再掉该方法如从数据库加载用户,然后添加到缓存中,下次调用时将会从缓存中获取到数据。自Spring 3.1起,提供了类似于Transactional注解事务的注解Cache支持,且提供了Cache抽象;在此之前一般通过AOP实现;使用Spring Cache的好处:提供基本的Cache抽象,方便切换各种底层Cache;通过注解Cache可以实现类似于事务一样,缓存逻辑透明的应用到我们的业务代码上,且只需要更少的代码就可以完成;提供事务回滚时也自动回滚缓存;支持比较复杂的缓存逻辑;对于Spring Cache抽象,主要从以下几个方面学习: Cache API及默认提供的实现 Cache注解 实现复杂的Cache逻辑Cache API及默认提供的实现Spring提供的核心Cache接口:Java代码1. packageorg.springframework.cache;2. 3. publicinterfaceCache4. StringgetName();/缓存的名字5. ObjectgetNativeCache();/得到底层使用的缓存,如Ehcache6. ValueWrapperget(Objectkey);/根据key得到一个ValueWrapper,然后调用其get方法获取值7. Tget(Objectkey,Classtype);/根据key,和value的类型直接获取value8. voidput(Objectkey,Objectvalue);/往缓存放数据9. voidevict(Objectkey);/从缓存中移除key对应的缓存10. voidclear();/清空缓存11. 12. interfaceValueWrapper/缓存值的Wrapper13. Objectget();/得到真实的value14. 15. 提供了缓存操作的读取/写入/移除方法;默认提供了如下实现:ConcurrentMapCache:使用java.util.concurrent.ConcurrentHashMap实现的Cache;GuavaCache:对Guava mon.cache.Cache进行的Wrapper,需要Google Guava 12.0或更高版本,since spring 4;EhCacheCache:使用Ehcache实现JCacheCache:对javax.cache.Cache进行的wrapper,since spring 3.2;spring4将此类更新到JCache 0.11版本;另外,因为我们在应用中并不是使用一个Cache,而是多个,因此Spring还提供了CacheManager抽象,用于缓存的管理:Java代码1. packageorg.springframework.cache;2. importjava.util.Collection;3. publicinterfaceCacheManager4. CachegetCache(Stringname);/根据Cache名字获取Cache5. CollectiongetCacheNames();/得到所有Cache的名字6. 默认提供的实现:ConcurrentMapCacheManager/ConcurrentMapCacheFactoryBean:管理ConcurrentMapCache;GuavaCacheManager;EhCacheCacheManager/EhCacheManagerFactoryBean;JCacheCacheManager/JCacheManagerFactoryBean;另外还提供了CompositeCacheManager用于组合CacheManager,即可以从多个CacheManager中轮询得到相应的Cache,如Java代码1. 2. 3. 4. 5. 6. 7. 8. 9. 当我们调用cacheManager.getCache(cacheName) 时,会先从第一个cacheManager中查找有没有cacheName的cache,如果没有接着查找第二个,如果最后找不到,因为fallbackToNoOpCache=true,那么将返回一个NOP的Cache否则返回null。除了GuavaCacheManager之外,其他Cache都支持Spring事务的,即如果事务回滚了,Cache的数据也会移除掉。Spring不进行Cache的缓存策略的维护,这些都是由底层Cache自己实现,Spring只是提供了一个Wrapper,提供一套对外一致的API。示例需要添加Ehcache依赖,具体依赖轻参考pom.xmlSpringCacheTest.javaJava代码1. Test2. publicvoidtest()throwsIOException3. /创建底层Cache4. net.sf.ehcache.CacheManagerehcacheManager5. =newnet.sf.ehcache.CacheManager(newClassPathResource(ehcache.xml).getInputStream();6. 7. /创建Spring的CacheManager8. EhCacheCacheManagercacheCacheManager=newEhCacheCacheManager();9. /设置底层的CacheManager10. cacheCacheManager.setCacheManager(ehcacheManager);11. 12. Longid=1L;13. Useruser=newUser(id,zhang,);14. 15. /根据缓存名字获取Cache16. Cachecache=cacheCacheManager.getCache(user);17. /往缓存写数据18. cache.put(id,user);19. /从缓存读数据20. Assert.assertNotNull(cache.get(id,User.class);21. 此处直接使用Spring提供的API进行操作;我们也可以通过xml/注解方式配置到spring容器;xml风格的(spring-cache.xml):Java代码1. 2. 3. 4. 5. 6. 7. 8. spring提供EhCacheManagerFactoryBean来简化ehcache cacheManager的创建,这样注入configLocation,会自动根据路径从classpath下找,比编码方式简单多了,然后就可以从spring容器获取cacheManager进行操作了。此处的transactionAware表示是否事务环绕的,如果true,则如果事务回滚,缓存也回滚,默认false。注解风格的(AppConfig.java):Java代码1. Bean2. publicCacheManagercacheManager()3. 4. try5. net.sf.ehcache.CacheManagerehcacheCacheManager6. =newnet.sf.ehcache.CacheManager(newClassPathResource(ehcache.xml).getInputStream();7. 8. EhCacheCacheManagercacheCacheManager=newEhCacheCacheManager(ehcacheCacheManager);9. returncacheCacheManager;10. catch(IOExceptione)11. thrownewRuntimeException(e);12. 13. 和编程方式差不多就不多介绍了。另外,除了这些默认的Cache之外,我们可以写自己的Cache实现;而且即使不用之后的Spring Cache注解,我们也尽量使用Spring Cache API进行Cache的操作,如果要替换底层Cache也是非常方便的。到此基本的Cache API就介绍完了,接下来我们来看看使用Spring Cache注解来简化Cache的操作。Cache注解启用Cache注解XML风格的(spring-cache.xml):Java代码1. 另外还可以指定一个key-generator,即默认的key生成策略,后边讨论;注解风格的(AppConfig.java):Java代码1. Configuration2. ComponentScan(basePackages=com.sishuok.spring.service)3. EnableCaching(proxyTargetClass=true)4. publicclassAppConfigimplementsCachingConfigurer5. Bean6. Override7. publicCacheManagercacheManager()8. 9. try10. net.sf.ehcache.CacheManagerehcacheCacheManager11. =newnet.sf.ehcache.CacheManager(newClassPathResource(ehcache.xml).getInputStream();12. 13. EhCacheCacheManagercacheCacheManager=newEhCacheCacheManager(ehcacheCacheManager);14. returncacheCacheManager;15. catch(IOExceptione)16. thrownewRuntimeException(e);17. 18. 19. 20. Bean21. Override22. publicKeyGeneratorkeyGenerator()23. returnnewSimpleKeyGenerator();24. 25. 1、使用EnableCaching启用Cache注解支持;2、实现CachingConfigurer,然后注入需要的cacheManager和keyGenerator;从spring4开始默认的keyGenerator是SimpleKeyGenerator;CachePut应用到写数据的方法上,如新增/修改方法,调用方法时会自动把相应的数据放入缓存:Java代码1. CachePut(value=user,key=#user.id)2. publicUsersave(Useruser)3. users.add(user);4. returnuser;5. 即调用该方法时,会把user.id作为key,返回值作为value放入缓存;CachePut注解:Java代码1. publicinterfaceCachePut2. Stringvalue();/缓存的名字,可以把数据写到多个缓存3. Stringkey()default;/缓存key,如果不指定将使用默认的KeyGenerator生成,后边介绍4. Stringcondition()default;/满足缓存条件的数据才会放入缓存,condition在调用方法之前和之后都会判断5. Stringunless()default;/用于否决缓存更新的,不像condition,该表达只在方法执行之后判断,此时可以拿到返回值result进行判断了6. CacheEvict即应用到移除数据的方法上,如删除方法,调用方法时会从缓存中移除相应的数据:Java代码1. CacheEvict(value=user,key=#user.id)/移除指定key的数据2. publicUserdelete(Useruser)3. users.remove(user);4. returnuser;5. 6. CacheEvict(value=user,allEntries=true)/移除所有数据7. publicvoiddeleteAll()8. users.clear();9. CacheEvict注解:Java代码1. publicinterfaceCacheEvict2. Stringvalue();/请参考CachePut3. Stringkey()default;/请参考CachePut4. Stringcondition()default;/请参考CachePut5. booleanallEntries()defaultfalse;/是否移除所有数据6. booleanbeforeInvocation()defaultfalse;/是调用方法之前移除/还是调用之后移除Cacheable应用到读取数据的方法上,即可缓存的方法,如查找方法:先从缓存中读取,如果没有再调用方法获取数据,然后把数据添加到缓存中:Java代码1. Cacheable(value=user,key=#id)2. publicUserfindById(finalLongid)3. System.out.println(cachemiss,invokefindbyid,id:+id);4. for(Useruser:users)5. if(user.getId().equals(id)6. returnuser;7. 8. 9. returnnull;10. Cacheable注解:Java代码1. publicinterfaceCacheable2. Stringvalue();/请参考CachePut3. Stringkey()default;/请参考CachePut4. Stringcondition()default;/请参考CachePut5. Stringunless()default;/请参考CachePut运行流程Java代码1. 1、首先执行CacheEvict(如果beforeInvocation=true且condition通过),如果allEntries=true,则清空所有2. 2、接着收集Cacheable(如果condition通过,且key对应的数据不在缓存),放入cachePutRequests(也就是说如果cachePutRequests为空,则数据在缓存中)3. 3、如果cachePutRequests为空且没有CachePut操作,那么将查找Cacheable的缓存,否则result=缓存数据(也就是说只要当没有cacheput请求时才会查找缓存)4. 4、如果没有找到缓存,那么调用实际的API,把结果放入result5. 5、如果有CachePut操作(如果condition通过),那么放入cachePutRequests6. 6、执行cachePutRequests,将数据写入缓存(unless为空或者unless解析结果为false);7. 7、执行CacheEvict(如果beforeInvocation=false且condition通过),如果allEntries=true,则清空所有流程中需要注意的就是2/3/4步:如果有CachePut操作,即使有Cacheable也不会从缓存中读取;问题很明显,如果要混合多个注解使用,不能组合使用CachePut和Cacheable;官方说应该避免这样使用(解释是如果带条件的注解相互排除的场景);不过个人感觉还是不要考虑这个好,让用户来决定如何使用,否则一会介绍的场景不能满足。提供的SpEL上下文数据Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:名字位置描述示例methodNameroot对象当前被调用的方法名#root.methodNamemethodroot对象当前被调用的方法#targetroot对象当前被调用的目标对象#root.targettargetClassroot对象当前被调用的目标对象类#root.targetClassargsroot对象当前被调用的方法的参数列表#root.args0cachesroot对象当前方法调用使用的缓存列表(如Cacheable(value=cache1, cache2)),则有两个cache#argument name执行上下文当前被调用的方法的参数,如findById(Long id),我们可以通过#id拿到参数#user.idresult执行上下文方法执行后的返回值(仅当方法执行之后的判断有效,如unless,cache evict的beforeInvocation=false)#result通过这些数据我们可能实现比较复杂的缓存逻辑了,后边再来介绍。Key生成器如果在Cache注解上没有指定key的话CachePut(value = user),会使用KeyGenerator进行生成一个key:Java代码1. publicinterfaceKeyGenerator2. Objectgenerate(Objecttarget,Methodmethod,Object.params);3. 默认提供了DefaultKeyGenerator生成器(Spring 4之后使用SimpleKeyGenerator):Java代码1. Override2. publicObjectgenerate(Objecttarget,Methodmethod,Object.params)3. if(params.length=0)4. returnSimpleKey.EMPTY;5. 6. if(params.length=1¶ms0!=null)7. returnparams0;8. 9. returnnewSimpleKey(params);10. 即如果只有一个参数,就使用参数作为key,否则使用SimpleKey作为key。我们也可以自定义自己的key生成器,然后通过xml风格的或注解风格的CachingConfigurer中指定keyGenerator。条件缓存根据运行流程,如下Cacheable将在执行方法之前( #result还拿不到返回值)判断condition,如果返回true,则查缓存;Java代码1. Cacheable(value=user,key=#id,condition=#idlt10)2. publicUserconditionFindById(finalLongid)根据运行流程,如下CachePut将在执行完方法后(#result就能拿到返回值了)判断condition,如果返回true,则放入缓存;Java代码1. CachePut(value=user,key=#id,condition=#result.usernamenezhang)2. publicUserconditionSave(finalUseruser)根据运行流程,如下CachePut将在执行完方法后(#result就能拿到返回值了)判断unless,如果返回false,则放入缓存;(即跟condition相反)Java代码1. CachePut(value=user,key=#user.id,unless=#result.usernameeqzhang)2. publicUserconditionSave2(finalUseruser)根据运行流程,如下CacheEvict,beforeInvocation=false表示在方法执行之后调用(#result能拿到返回值了);且判断condition,如果返回true,则移除缓存;Java代码1. CacheEvict(value=user,key=#user.id,beforeInvocation=false,condition=#result.usernamenezhang)2. publicUserconditionDelete(finalUseruser)Caching有时候我们可能组合多个Cache注解使用;比如用户新增成功后,我们要添加id-user;username-user;email-user的缓存;此时就需要Caching组合多个注解标签了。如用户新增成功后,添加id-user;username-user;email-user到缓存;Java代码1. Caching(2. put=3. CachePut(value=user,key=#user.id),4. CachePut(value=user,key=#user.username),5. CachePut(value=user,key=#user.email)6. 7. )8. publicUsersave(Useruser)Caching定义如下:Java代码1. publicinterfaceCaching2. Cacheablecacheable()default;/声明多个Cacheable3. CachePutput()default;/声明多个CachePut4. CacheEvictevict()default;/声明多个CacheEvict5. 自定义缓存注解比如之前的那个Caching组合,会让方法上的注解显得整个代码比较乱,此时可以使用自定义注解把这些注解组合到一个注解中,如:Java代码1. Caching(2. put=3. CachePut(value=user,key=#user.id),4. CachePut(value=user,key=#user.username),5. CachePut(value=user,key=#user.email)6. 7. )8. Target(ElementType.METHOD,ElementType.TYPE)9. Retention(RetentionPolicy.RUNTIME)10. Inherited11. publicinterfaceUserSaveCache12. 这样我们在方法上使用如下代码即可,整个代码显得比较干净。Java代码1. UserSaveCache2. publicUsersave(Useruser)示例新增/修改数据时往缓存中写Java代码1. Caching(2. put=3. CachePut(value=user,key=#user.id),4. CachePut(value=user,key=#user.username),5. CachePut(value=user,key=#user.email)6. 7. )8. publicUsersave(Useruser)Java代码1. Caching(2. put=3. CachePut(value=user,key=#user.id),4. CachePut(value=user,key=#user.username),5. CachePut(value=user,key=#user.email)6. 7. )8. publicUserupdate(Useruser)删除数据时从缓存中移除Java代码1. Caching(2. evict=3. CacheEvict(value=user,key=#user.id),4. CacheEvict(value=user,key=#user.username),5. CacheEvict(value=user,key=#user.email)6. 7. )8. publicUserdelete(Useruser)Java代码1. CacheEvict(value=user,allEntries=true)2. publicvoiddeleteAll()查找时从缓存中读Java代码1. Caching(2. cacheable=3. Cacheable(value=user,key=#id)4. 5. )6. publicUserfindById(finalLongid)Java代码1. Caching(2. cacheable=3. Cacheable(value=user,key=#username)4. 5. )6. publicUserfindByUsername(finalStringusername)Java代码1. Caching(2. cacheable=3. Cacheable(value=user,key=#email)4. 5. )6. publicUserfindByEmail(finalStringemail)问题及解决方案一、比如findByUsername时,不应该只放username-user,应该连同id-user和email-user一起放入;这样下次如果按照id查找直接从缓存中就命中了;这需要根据之前的运行流程改造CacheAspectSupport:Java代码1. /Weonlyattempttogetacachedresultiftherearenoputrequests2. if(cachePutRequests.isEmpty()&contexts.get(CachePutOperation.class).isEmpty()3. result=findCachedResult(contexts.get(CacheableOperation.class);4. 改为:Java代码1. CollectioncacheOperationContexts=contexts.get(CacheableOperation.class);2. if(!cacheOperationContexts.isEmpty()3. result=findCachedResult(cacheOperationContexts);4. 然后就可以通过如下代码完成想要的功能:Java代码1. Caching(2. cacheable=3. Cacheable(value=user,key=#username)4. ,5. put=6. CachePut(value=user,key=#result.id,condition=#result!=null),7. CachePut(value=user,key=#result.email,condition=#result!=null)8. 9. )10. publicUserfindByUsername(finalStringusername)11. System.out.println(cachemiss,invokefindbyusername,username:+username);12. for(Useruser:users)13. if(user.getUsername().equals(username)14. returnuser;15. 16. 17. returnnull;18. 二、缓存注解会让代码看上去比较乱;应该使用自定义注解把缓存注解提取出去;三、往缓存放数据/移除数据是有条件的,而且条件可能很复杂,考虑使用SpEL表达式:Java代码1. CacheEvict(value=user,key=#user.id,condition=#root.target.canCache()and#root.caches0.get(#user.id).get().usernamene#user.username,beforeInvocation=true)2. publicvoidconditionUpdate(Useruser)或更复杂的直接调用目标对象的方法进行操作(如只有修改了某个数据才从缓存中清除,比如菜单数据的缓存,只有修改了关键数据时才清空菜单对应的权限数据)Java代码1. Caching(2. evict=3. CacheEvict(value=user,key=#user.id,condition=#root.target.canEvict(#root.caches0,#user.id,#user.username),beforeInvocation=true)4. 5. )6. publicvoidcondi
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 人教版科学六年级上册第二章第3课《气体在人体中的进出》课件1
- 《第11章多彩的物质世界》复习课件
- 通信原理第7版第1章课件樊昌信版
- 资金管理风险防控管理制度
- 重点人群急救技能培训实施方案
- 实验室高温防护专项培训课件
- 基层公共卫生协管员轮训实施方案
- 水利工程竣工验收管理规程
- 生物质颗粒燃料项目可行性研究报告
- 家庭急救知识普及实施方案
- 2026年新教材人教版九年级上册英语期中复习:Unit 1-4+期中5套测试卷汇编(含答案)
- 河北省邯郸市2025-2026学年高一上学期期中联考语文试题及答案
- ESC 2026新版心力衰竭管理指南精读
- 2024+ESO指南:卵圆孔未闭患者卒中后诊断与治疗
- 2026智联招聘笔试题库及答案
- 2026版《药物临床试验质量管理规范》GCP考试题库(含标准答案+解析)
- EDA技术及应用项目教程 教学大纲
- 中医护理评估
- 2025年度中国展览数据统计报告
- 2026年中考作文押题指导
- 全新版大学英语视听说教程1答案
评论
0/150
提交评论