也聊缓存

缓存到底扮演了什么角色

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


在对项目进行优化的时候,我们可以主要从以下三个方面入手:
1 缓存
2 集群
3 异步


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


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

spring的三个注解

@Cacheable

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


在对上面的方法加上 @Cacheable(value="accountCache")后,第一次使用某个参数例如"张三"查询的时候,会执行getAccountByName的方法体,并且可以认为在spring内部会生成一个名叫accountCache的hashmap,里面的key就是"张三",value就是getAccountByName方法的返回值。
当第二次以参数"张三"调用getAccountByName的时候,spring就会先在accountCache里面找有没有哪个key是"张三",结果发现有,那就直接返回,也就不会执行getAccountByName的方法体了。
我再说简单点,第二次调用getAccountByName的时候(以张三为参数),就不会打印出"我进来了"。
说了这么久,那么accountCache是个什么东西呢?
请看下面的配置文件:
    <cache:annotation-driven/>


    <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                             
                <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
                    <property name="name" value="accountCache"/>
                </bean>
            </set>
        </property>
    </bean>

 @CacheEvict

我们提到了缓存,上面的例子说明了建立缓存,那总得有方法来修改缓存吧。
    
    @CacheEvict(value="accountCache",key="#account.getName()")
    public Account updateAccount(Account account) {
        return updateDB(account);
    }
上面的意思是说,如果一旦调用了updateAccount这个方法,spring就会通过参数的getName方法获得一个值,并且用这个值删除accountCache中的某一个key。
这样做的效果就是,如果update某个对象后,这个对象在accountCache中的记录就没有了。
另外
    @CacheEvict(value="accountCache",allEntries=true)
    public void reload() {
    }
参数allEntyies=true是什么意思,还用我说么?


 @CachePut

上面的方法感觉还是有点麻烦,我想更新缓存,而不想删除缓存。
    @CachePut(value="accountCache",key="#account.getName()")
    public Account updateAccount(Account account) { 
      return updateDB(account); 
    } 
上面的 @CachePut的意思就是说,一旦我调用updateAccount方法,spring就会通过getName()取得account参数的一个值,并且以这个值为key,以updateAccount的返回值为value,更新accountCache。

condition与多参数

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

关于condition

@Cacheable(value="accountCache",condition="#accountName.length() <= 4")
public Account getAccountByNameWithCondition(String accountName) {
    // 方法内部实现不考虑缓存逻辑,直接实现业务
    return getFromDB(accountName);
}
condaition会返回一个boolean值,如果是true,才进行缓存操作。
没看懂么?好吧,我自己也不是很清楚这句话。
accountService5.getAccountByNameWithCondition("dlf love glt");
    accountService5.getAccountByNameWithCondition("dlf love glt");
    accountService5.getAccountByNameWithCondition("dlf love glt");
运行这三行,每一行都会进入方法体,仿佛并没有关于缓存的任何事情。

关于多个参数的遴选

 @Cacheable(value="accountCache",key="#accountName.concat(#password)") 
 public Account getAccount(String accountName,String password,boolean other) { 
   // 方法内部实现不考虑缓存逻辑,直接实现业务
   return getFromDB(accountName,password); 
 }
首先说明额,那个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; 
 import java.util.Map; 


 import org.springframework.cache.Cache; 
 import org.springframework.cache.support.SimpleValueWrapper; 


 public class MyCache implements Cache { 
   private String name; 
   private Map<String,Account> store = new HashMap<String,Account>();; 
  
   public MyCache() { 
   } 
  
   public MyCache(String name) { 
     this.name = name; 
   } 
  
   @Override 
   public String getName() { 
     return name; 
   } 
  
   public void setName(String name) { 
     this.name = name; 
   } 


   @Override 
   public Object getNativeCache() { 
     return store; 
   } 


   @Override 
   public ValueWrapper get(Object key) { 
     ValueWrapper result = null; 
     Account thevalue = store.get(key); 
     if(thevalue!=null) { 
       thevalue.setPassword("from mycache:"+name);   //注意这一行
       result = new SimpleValueWrapper(thevalue); 
     } 
     return result; 
   } 


   @Override 
   public void put(Object key, Object value) { 
     Account thevalue = (Account)value; 
     store.put((String)key, thevalue); 
   } 


   @Override 
   public void evict(Object key) { 
   } 


   @Override 
   public void clear() { 
   } 
 }


 再看CacheManager
import java.util.Collection;
import org.springframework.cache.support.AbstractCacheManager;




public class MyCacheManager extends AbstractCacheManager { 
	 
	   private Collection<? extends MyCache> caches; 
	  
	   /** 
	   * Specify the collection of Cache instances to use for this CacheManager. 
	   */ 
	   public void setCaches(Collection<? extends MyCache> caches) { 
	     this.caches = caches; 
	   } 


	   @Override 
	   protected Collection<? extends MyCache> loadCaches() { 
	     return this.caches; 
	   } 


	 }
上面的一切都已经OK,当然spring还不知道我们干的事情
所以,看xml
 <cache:annotation-driven /> 


 <bean id="cacheManager" class="com.cache.springannotation.step6.MyCacheManager">
     <property name="caches"> 
       <set> 
         <bean 
           class="com.rollenholt.spring.cache.MyCache"
           p:name="accountCache" /> 
       </set> 
     </property> 
   </bean> 
当我们用下面的代码测试的时候:
    @Test
    public void getCacheMySelf(){
    	Account account = accountService6.getAccountByName("someone"); 
    	System.out.println("***");
    	logger.info("passwd={}", account.getPassword()); 
    	account = accountService6.getAccountByName("someone"); 
    	logger.info("passwd={}", account.getPassword()); 
    }
输出如下:
INFO  2015-12-04 21:07:01,695 cache.springannotation.step6.AccountService6:26 --- real querying account... someone
INFO  2015-12-04 21:07:01,697 cache.springannotation.step6.AccountService6:60 --- real querying db... someone
***
INFO  2015-12-04 21:07:01,697 cache.springannotation.step6.AccountService6Test:74 --- passwd=null
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,AOP,cache)