谈谈spring的缓存
缓存到底扮演了什么角色
请移步: 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的缓存的更多相关文章
- 谈谈Spring中都用到了哪些设计模式?
谈谈Spring中都用到了哪些设计模式? JDK 中用到了那些设计模式?Spring 中用到了那些设计模式?这两个问题,在面试中比较常见.我在网上搜索了一下关于 Spring 中设计模式的讲解几乎都是 ...
- 注释驱动的 Spring cache 缓存介绍
概述 Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使 ...
- [转]注释驱动的 Spring cache 缓存介绍
原文:http://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/ 概述 Spring 3.1 引入了激动人心的基于注释(an ...
- 注释驱动的 Spring cache 缓存介绍--转载
概述 Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使 ...
- Spring实战——缓存
缓存 提到缓存,你能想到什么?一级缓存,二级缓存,web缓存,redis-- 你所能想到的各种包罗万象存在的打着缓存旗号存在的各种技术或者实现,无非都是宣扬缓存技术的优势就是快,无需反复查询等. 当然 ...
- Spring 对缓存的抽象
Cache vs. Buffer A buffer is used traditionally as an intermediate temporary store for data between ...
- mybatis中两种取值方式?谈谈Spring框架理解?
1.mybatis中两种取值方式? 回答:Mybatis中取值方式有几种?各自区别是什么? Mybatis取值方式就是说在Mapper文件中获取service传过来的值的方法,总共有两种方式,通过 $ ...
- Spring之缓存注解@Cacheable
https://www.cnblogs.com/fashflying/p/6908028.html https://blog.csdn.net/syani/article/details/522399 ...
- Java缓存学习之五:spring 对缓存的支持
(注意标题,Spring对缓存的支持 这里不单单指Ehcache ) 从3.1开始,Spring引入了对Cache的支持.其使用方法和原理都类似于Spring对事务管理的支持.Spring Cache ...
随机推荐
- 【HNOI2002】【矩阵快速幂】公交车路线
仍然是学弟出的题目的原题@lher 学弟将题目改成了多组数据,n在ll范围内,所以我就只讲提高版的做法. 链接:https://www.luogu.org/problem/show?pid=2233 ...
- 【NOIP2016】换教室
题目描述 对于刚上大学的牛牛来说, 他面临的第一个问题是如何根据实际情况中情合适的课程. 在可以选择的课程中,有2n节课程安排在n个时间段上.在第 i ( 1≤ i≤n)个时同段上, 两节内容相同的课 ...
- 【NOIP2004】虫食算
Description 所谓虫食算,就是原先的算式中有一部分被虫子啃掉了,需要我们根据剩下的数字来判定被啃掉的字母.来看一个简单的例子: 43#9865#045 +. 8468#6633 444455 ...
- POJ 3171 Cleaning Shifts
Description Farmer John's cows, pampered since birth, have reached new heights of fastidiousness. Th ...
- BZOJ4574 [Zjoi2016]线段树
比较厉害的dp. 网上题解都是利用了随机的条件,用了一个$O(n^4)$的dp,这里简单说一下. 用f(x,i,l,r)表示经过前i轮操作,[l,r]的所有数<=x,且l-1和r+1都>x ...
- PHP+JQuery+Ajax初始化网站基本信息(附源码)--PHP
一.思路 为了保存用户会员信息的时间长一些,不局限于session的关闭.我们需要将用户信息保存在数据库中,前台每次登录都需要进行校验,来查看用看用户会员信息是否过期,如果没有过期,取出用户会员信息存 ...
- java怎样获取CPU占用率和硬盘占用率
通过jmx可以监控vm内存使用,系统内存使用等,以下是网上某博客代码,特点是通过window和linux命令获得CPU使用率. 利用java程序实现获取计算机cpu利用率和内存使用信息. packag ...
- JavaScript正则表达式模式匹配(2)——分组模式匹配
var pattern=/google{4,8}$/; // {4,8}$表示匹配结尾4-8次 var str='googleeeeeeeee'; // 表示e的4-8次 alert(pattern. ...
- linkList hashSet ArrayList IO 序列化 1.1.瞬态transient .字符编码表 Properties
Day12 IO 序列化 .递归_递归的概念_注意事项 1.递归:方法的递归调用--它是一种方法调用的方式--方法可以调用其本身 2.注意事项: 1).递归必须要有一个"出口(结束的条 ...
- Spring完全基于Java配置和集成Junit单元测试
要点: 配置继承WebApplicationInitializer的类作为启动类,相当于配置web.xml文件 使用@Configuration注解一个类,在类中的方式使用@Bean注解,则表名该方法 ...