缓存到底扮演了什么角色

请移步:  http://hacpai.com/article/1376986299174

在对项目进行优化的时候,我们可以主要从以下三个方面入手:

1 缓存

2 集群

3 异步





今天,就先说说缓存。先说说spring的缓存,下一节我们再聊聊redis





说到spring的缓存,得提到三个注解。

spring的三个注解

@Cacheable

  1. @Cacheable(value="accountCache")
  2. public Account getAccountByName(String accountName) {
  3. System.out.println("我进来了");
  4. // 方法内部实现不考虑缓存逻辑,直接实现业务
  5. logger.info("real querying account... {}", accountName);
  6. Account accountOptional = getFromDB(accountName);
  7. return accountOptional;
  8. }

在对上面的方法加上 @Cacheable(value="accountCache")后,第一次使用某个参数例如"张三"查询的时候,会执行getAccountByName的方法体,并且可以认为在spring内部会生成一个名叫accountCache的hashmap,里面的key就是"张三",value就是getAccountByName方法的返回值。

当第二次以参数"张三"调用getAccountByName的时候,spring就会先在accountCache里面找有没有哪个key是"张三",结果发现有,那就直接返回,也就不会执行getAccountByName的方法体了。

我再说简单点,第二次调用getAccountByName的时候(以张三为参数),就不会打印出"我进来了"。

说了这么久,那么accountCache是个什么东西呢?

请看下面的配置文件:

  1. <cache:annotation-driven/>
  2.  
  3. <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
  4. <property name="caches">
  5. <set>
  6.  
  7. <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
  8. <property name="name" value="accountCache"/>
  9. </bean>
  10. </set>
  11. </property>
  12. </bean>

@CacheEvict

我们提到了缓存,上面的例子说明了建立缓存,那总得有方法来修改缓存吧。

  1. @CacheEvict(value="accountCache",key="#account.getName()")
  2. public Account updateAccount(Account account) {
  3. return updateDB(account);
  4. }

上面的意思是说,如果一旦调用了updateAccount这个方法,spring就会通过参数的getName方法获得一个值,并且用这个值删除accountCache中的某一个key。

这样做的效果就是,如果update某个对象后,这个对象在accountCache中的记录就没有了。

另外

  1. @CacheEvict(value="accountCache",allEntries=true)
  2. public void reload() {
  3. }

参数allEntyies=true是什么意思,还用我说么?

@CachePut

上面的方法感觉还是有点麻烦,我想更新缓存,而不想删除缓存。

  1. @CachePut(value="accountCache",key="#account.getName()")
  2. public Account updateAccount(Account account) {
  3. return updateDB(account);
  4. }

上面的 @CachePut的意思就是说,一旦我调用updateAccount方法,spring就会通过getName()取得account参数的一个值,并且以这个值为key,以updateAccount的返回值为value,更新accountCache。

condition与多参数

3个最主要的标签的最主要的方法说完了,spring关于缓存还有一点小的"奇技淫巧"(呵呵,张晨的惯用语)我们一块来看看

关于condition

  1. @Cacheable(value="accountCache",condition="#accountName.length() <= 4")
  2. public Account getAccountByNameWithCondition(String accountName) {
  3. // 方法内部实现不考虑缓存逻辑,直接实现业务
  4. return getFromDB(accountName);
  5. }

condaition会返回一个boolean值,如果是true,才进行缓存操作。

没看懂么?好吧,我自己也不是很清楚这句话。

accountService5.getAccountByNameWithCondition("dlf love glt");

    accountService5.getAccountByNameWithCondition("dlf love glt");

    accountService5.getAccountByNameWithCondition("dlf love glt");

运行这三行,每一行都会进入方法体,仿佛并没有关于缓存的任何事情。

关于多个参数的遴选

  1. @Cacheable(value="accountCache",key="#accountName.concat(#password)")
  2. public Account getAccount(String accountName,String password,boolean other) {
  3. // 方法内部实现不考虑缓存逻辑,直接实现业务
  4. return getFromDB(accountName,password);
  5. }

首先说明额,那个concat是java中String类的原生方法。

可是我把key="#accountName.concat(#password)"改成key="#accountName#password"就不成。至于为什么,暂时还没有考虑。

如果 @Cacheable中只有value参数,没有key参数,那么方法的所有参数将作为一个联合主键成为map中的key。如果是上面例子中的情况,虽然有三个参数,但是spring只会把accountName与password作为联合主键。

关于这个,大家都理解了吧。

如果大家都理解了,我这还有一个问题:就像上面的例子,咱们假定,第三个参数表示是否打印日志。

可是一旦前两个参数都已经存在于spirng的cache中了,这个方法体都不会运行了,那第三个参数还有个毛用?

直接把取数据的那段逻辑抽出来做一个方法,而且它只有两个参数,不就OK了?

最后我的想法是:

就算第三个参数是关于打印日志的,我第二次操作的时候,就没有访问数据库,也就不用打印日志了,所以这个关于遴选参数的key还是有用的。

扩展性

其实,就上面的内容而已,已经够我们在绝大部分场合使用了。

不过有时候,我们还需要扩展。例如,我想持久化缓存,但是我又不想侵入现有的代码。(我这个需求很操蛋,但就是为了说明这么一个场景:我们需要自定义缓存方案)

首先,我们需要提供一个 CacheManager 接口的实现,这个接口告诉 spring 有哪些 cache 实例,spring 会根据 cache 的名字查找 cache 的实例。另外还需要自己实现 Cache 接口,Cache 接口负责实际的缓存逻辑,例如增加键值对、存储、查询和清空等。



我们自定义了一个缓存,它的名字叫MyCache

 import java.util.HashMap; 

  1. import java.util.Map;
  2.  
  3. import org.springframework.cache.Cache;
  4. import org.springframework.cache.support.SimpleValueWrapper;
  5.  
  6. public class MyCache implements Cache {
  7. private String name;
  8. private Map<String,Account> store = new HashMap<String,Account>();;
  9.  
  10. public MyCache() {
  11. }
  12.  
  13. public MyCache(String name) {
  14. this.name = name;
  15. }
  16.  
  17. @Override
  18. public String getName() {
  19. return name;
  20. }
  21.  
  22. public void setName(String name) {
  23. this.name = name;
  24. }
  25.  
  26. @Override
  27. public Object getNativeCache() {
  28. return store;
  29. }
  30.  
  31. @Override
  32. public ValueWrapper get(Object key) {
  33. ValueWrapper result = null;
  34. Account thevalue = store.get(key);
  35. if(thevalue!=null) {
  36. thevalue.setPassword("from mycache:"+name); //注意这一行
  37. result = new SimpleValueWrapper(thevalue);
  38. }
  39. return result;
  40. }
  41.  
  42. @Override
  43. public void put(Object key, Object value) {
  44. Account thevalue = (Account)value;
  45. store.put((String)key, thevalue);
  46. }
  47.  
  48. @Override
  49. public void evict(Object key) {
  50. }
  51.  
  52. @Override
  53. public void clear() {
  54. }
  55. }
  56.  
  57. 再看CacheManager
  58. import java.util.Collection;
  59. import org.springframework.cache.support.AbstractCacheManager;
  60.  
  61. public class MyCacheManager extends AbstractCacheManager {
  62.  
  63. private Collection<? extends MyCache> caches;
  64.  
  65. /**
  66. * Specify the collection of Cache instances to use for this CacheManager.
  67. */
  68. public void setCaches(Collection<? extends MyCache> caches) {
  69. this.caches = caches;
  70. }
  71.  
  72. @Override
  73. protected Collection<? extends MyCache> loadCaches() {
  74. return this.caches;
  75. }
  76.  
  77. }

上面的一切都已经OK,当然spring还不知道我们干的事情

所以,看xml

  1. <cache:annotation-driven />
  2.  
  3. <bean id="cacheManager" class="com.cache.springannotation.step6.MyCacheManager">
  4. <property name="caches">
  5. <set>
  6. <bean
  7. class="com.rollenholt.spring.cache.MyCache"
  8. p:name="accountCache" />
  9. </set>
  10. </property>
  11. </bean>

当我们用下面的代码测试的时候:

  1. @Test
  2. public void getCacheMySelf(){
  3. Account account = accountService6.getAccountByName("someone");
  4. System.out.println("***");
  5. logger.info("passwd={}", account.getPassword());
  6. account = accountService6.getAccountByName("someone");
  7. logger.info("passwd={}", account.getPassword());
  8. }

输出如下:

  1. INFO 2015-12-04 21:07:01,695 cache.springannotation.step6.AccountService6:26 --- real querying account... someone
  2. INFO 2015-12-04 21:07:01,697 cache.springannotation.step6.AccountService6:60 --- real querying db... someone
  3. ***
  4. INFO 2015-12-04 21:07:01,697 cache.springannotation.step6.AccountService6Test:74 --- passwd=null
  5. INFO 2015-12-04 21:07:01,698 cache.springannotation.step6.AccountService6Test:76 --- passwd=from mycache:accountCache

注意和限制

上面介绍过 spring cache 的原理,即它是基于动态生成的 proxy 代理机制来对方法的调用进行切面,这里关键点是对象的引用问题.



如果对象的方法是内部调用(即 this 引用)而不是外部引用,则会导致 proxy 失效,那么我们的切面就失效,也就是说上面定义的各种注释包括 @Cacheable、@CachePut 和 @CacheEvict 都会失效,我们来演示一下。





public Account getAccountByName2(String accountName) { 

   return this.getAccountByName(accountName); 

 } 



 @Cacheable(value="accountCache")// 使用了一个缓存名叫 accountCache 

 public Account getAccountByName(String accountName) { 

   // 方法内部实现不考虑缓存逻辑,直接实现业务

   return getFromDB(accountName); 

 }

 上面我们定义了一个新的方法 getAccountByName2,其自身调用了 getAccountByName 方法,这个时候,发生的是内部调用(this),所以没有走 proxy,导致 spring cache 失效

简单的说:

spirng默认的缓存不支持内部调用

而且要缓存的方法必须是public的



就在刚才

glt想我了

glt是谁? 我媳妇

嘿嘿

参考资料:

我这篇博客基本上可以认为是对下面这篇博客做的读书笔记

http://www.cnblogs.com/rollenholt/p/4202631.html

rollenholt这篇文章写得很好,它的主要参考文章也是下面这篇

http://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/

谈谈spring的缓存的更多相关文章

  1. 谈谈Spring中都用到了哪些设计模式?

    谈谈Spring中都用到了哪些设计模式? JDK 中用到了那些设计模式?Spring 中用到了那些设计模式?这两个问题,在面试中比较常见.我在网上搜索了一下关于 Spring 中设计模式的讲解几乎都是 ...

  2. 注释驱动的 Spring cache 缓存介绍

    概述 Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使 ...

  3. [转]注释驱动的 Spring cache 缓存介绍

    原文:http://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/ 概述 Spring 3.1 引入了激动人心的基于注释(an ...

  4. 注释驱动的 Spring cache 缓存介绍--转载

    概述 Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使 ...

  5. Spring实战——缓存

    缓存 提到缓存,你能想到什么?一级缓存,二级缓存,web缓存,redis-- 你所能想到的各种包罗万象存在的打着缓存旗号存在的各种技术或者实现,无非都是宣扬缓存技术的优势就是快,无需反复查询等. 当然 ...

  6. Spring 对缓存的抽象

    Cache vs. Buffer A buffer is used traditionally as an intermediate temporary store for data between ...

  7. mybatis中两种取值方式?谈谈Spring框架理解?

    1.mybatis中两种取值方式? 回答:Mybatis中取值方式有几种?各自区别是什么? Mybatis取值方式就是说在Mapper文件中获取service传过来的值的方法,总共有两种方式,通过 $ ...

  8. Spring之缓存注解@Cacheable

    https://www.cnblogs.com/fashflying/p/6908028.html https://blog.csdn.net/syani/article/details/522399 ...

  9. Java缓存学习之五:spring 对缓存的支持

    (注意标题,Spring对缓存的支持 这里不单单指Ehcache ) 从3.1开始,Spring引入了对Cache的支持.其使用方法和原理都类似于Spring对事务管理的支持.Spring Cache ...

随机推荐

  1. [济南集训 2017] 求gcd之和

    题目大意: 求\(\sum_{i=1}^n\sum_{j=1}^mgcd(i,j)\) 解题报告: 有一个结论:一个数的所有因子的欧拉函数之和等于这个数本身 运用这个我们可以开始推: \(\sum_{ ...

  2. 存储单位的换算(KB, MB, GB)

    关于存储单位的换算,大家一般会想到下面的换算方法. 1GB=1024MB 1MB=1024KB 1kb=1024字节 但实际生活中,这种换算方法并不准确. 例如在商家生产销售的硬盘, U盘中就不是这样 ...

  3. python中decode

    这 是因为遇到了非法字符--尤其是在某些用C/C++编写的程序中,全角空格往往有多种不同的实现方式,比如\xa3\xa0,或者\xa4\x57,这些 字符,看起来都是全角空格,但它们并不是" ...

  4. 美团、java后台实习、面经

    3月27号投了美团java后台,29号收到面试邀请,好像是金融服务平台(提交简历的时候,我当时没注意随便填的···) 一面: 介绍项目经历 根据简历问一些问题:比如我简历上有区块链相关,会要求介绍一下 ...

  5. asp.net用户控件引用

    <%@ Register Src="~/_module/IndexChannelHead.ascx" TagName="tn" TagPrefix=&qu ...

  6. Memcached在Linux环境下的使用详解

    一.引言             写有关NoSQL数据库有关的文章已经有一段时间了,可以高兴的说,Redis暂时就算写完了,从安装到数据类型,在到集群,几乎都写到了.如果以后有了心得,再补充吧.然后就 ...

  7. Git 中 SSH key 生成步骤

    由于本地Git仓库和GitHub仓库之间的传输是通过SSH加密的,所以必须要让github仓库认证你SSH key,在此之前,必须要生成SSH key. 第1步:创建SSH Key.在windows下 ...

  8. JavaScript基础知识从浅入深理解(一)

    JavaScript的简介 javascript是一门动态弱类型的解释型编程语言,增强页面动态效果,实现页面与用户之间的实时动态的交互. javascript是由三部分组成:ECMAScript.DO ...

  9. @RequestBody注解用法

    做Java已经有8个多月了,但是基本没有学习过Java语言,因此在项目中写代码基本靠的是其他语言的基础来写Java代码,写出来的很多代码虽然能用,但是感觉很不地道,虽然从来没有同事说过,但是我自己觉得 ...

  10. Docker常见仓库MySQL

    MySQL 基本信息 MySQL 是开源的关系数据库实现. 该仓库提供了 MySQL 各个版本的镜像,包括 5.6 系列.5.7 系列等. 使用方法 默认会在 3306 端口启动数据库. $ sudo ...