springcache使用笔记001_注释驱动的 Spring cache 简单使用
spring的缓存介绍:
Spring 3.1 引入了基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果。
Spring 的缓存技术还具备相当的灵活性,不仅能够使用 SpEL(Spring Expression Language)来定义缓存的 key 和各种 condition,还提供开箱即用的缓存临时存储方案,也支持和主流的专业缓存例如 EHCache 集成。
其特点总结如下:
- 通过少量的配置 annotation 注释即可使得既有代码支持缓存
- 支持开箱即用 Out-Of-The-Box,即不用安装和部署额外第三方组件即可使用缓存
- 支持 Spring Express Language,能使用对象的任何属性或者方法来定义缓存的 key 和 condition
- 支持 AspectJ,并通过其实现任何方法的缓存支持
- 支持自定义 key 和自定义缓存管理者,具有相当的灵活性和扩展性
---------------------
先看一看没有用spring以前,咱们是如何自定义一个缓存来使用的
这里先展示一个完全自定义的缓存实现,即不用任何第三方的组件来实现某种对象的内存缓存。
场景是:对一个账号查询方法做缓存,以账号名称为 key,账号对象为 value,当以相同的账号名称查询账号的时候,直接从缓存中返回结果,否则更新缓存。账号查询服务还支持 reload 缓存(即清空缓存)。
首先定义一个实体类:账号类,具备基本的 id 和 name 属性,且具备 getter 和 setter 方法
清单 1. Account.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package cacheOfAnno;
public class Account {
private int id;
private String name;
public Account(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
|
然后定义一个缓存管理器,这个管理器负责实现缓存逻辑,支持对象的增加、修改和删除,支持值对象的泛型。如下:
清单 2. MyCacheManager.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
package oldcache;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class MyCacheManager< T > {
private Map< String ,T> cache =
new ConcurrentHashMap< String ,T>();
public T getValue(Object key) {
return cache.get(key);
}
public void addOrUpdateCache(String key,T value) {
cache.put(key, value);
}
public void evictCache(String key) {// 根据 key 来删除缓存中的一条记录
if(cache.containsKey(key)) {
cache.remove(key);
}
}
public void evictCache() {// 清空缓存中的所有记录
cache.clear();
}
}
|
好,现在我们有了实体类和一个缓存管理器,还需要一个提供账号查询的服务类,此服务类使用缓存管理器来支持账号查询缓存,如下:
清单 3. MyAccountService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
package oldcache;
import cacheOfAnno.Account;
public class MyAccountService {
private MyCacheManager< Account > cacheManager;
public MyAccountService() {
cacheManager = new MyCacheManager< Account >();// 构造一个缓存管理器
}
public Account getAccountByName(String acctName) {
Account result = cacheManager.getValue(acctName);// 首先查询缓存
if(result!=null) {
System.out.println("get from cache..."+acctName);
return result;// 如果在缓存中,则直接返回缓存的结果
}
result = getFromDB(acctName);// 否则到数据库中查询
if(result!=null) {// 将数据库查询的结果更新到缓存中
cacheManager.addOrUpdateCache(acctName, result);
}
return result;
}
public void reload() {
cacheManager.evictCache();
}
private Account getFromDB(String acctName) {
System.out.println("real querying db..."+acctName);
return new Account(acctName);
}
}
|
现在我们开始写一个测试类,用于测试刚才的缓存是否有效
清单 4. Main.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package oldcache;
public class Main {
public static void main(String[] args) {
MyAccountService s = new MyAccountService();
// 开始查询账号
s.getAccountByName("somebody");// 第一次查询,应该是数据库查询
s.getAccountByName("somebody");// 第二次查询,应该直接从缓存返回
s.reload();// 重置缓存
System.out.println("after reload...");
s.getAccountByName("somebody");// 应该是数据库查询
s.getAccountByName("somebody");// 第二次查询,应该直接从缓存返回
}
}
|
按照分析,执行结果应该是:首先从数据库查询,然后直接返回缓存中的结果,重置缓存后,应该先从数据库查询,然后返回缓存中的结果,实际的执行结果如下:
清单 5. 运行结果
1
2
3
4
5
|
real querying db...somebody// 第一次从数据库加载
get from cache...somebody// 第二次从缓存加载
after reload...// 清空缓存
real querying db...somebody// 又从数据库加载
get from cache...somebody// 从缓存加载
|
可以看出我们的缓存起效了,但是这种自定义的缓存方案有如下劣势:
- 缓存代码和业务代码耦合度太高,如上面的例子,AccountService 中的 getAccountByName()方法中有了太多缓存的逻辑,不便于维护和变更
- 不灵活,这种缓存方案不支持按照某种条件的缓存,比如只有某种类型的账号才需要缓存,这种需求会导致代码的变更
- 缓存的存储这块写的比较死,不能灵活的切换为使用第三方的缓存模块
如果你的代码中有上述代码的影子,那么你可以考虑按照下面的介绍来优化一下你的代码结构了,也可以说是简化,你会发现,你的代码会变得优雅的多!
Hello World,注释驱动的 Spring Cache
Hello World 的实现目标
本 Hello World 类似于其他任何的 Hello World 程序,从最简单实用的角度展现 spring cache 的魅力,它基于刚才自定义缓存方案的实体类 Account.java,重新定义了 AccountService.java 和测试类 Main.java(注意这个例子不用自己定义缓存管理器,因为 spring 已经提供了缺省实现)
需要的 jar 包
为了实用 spring cache 缓存方案,在工程的 classpath 必须具备下列 jar 包。
图 1. 工程依赖的 jar 包图
注意这里我引入的是最新的 spring 3.2.0.M1 版本 jar 包,其实只要是 spring 3.1 以上,都支持 spring cache。其中 spring-context-*.jar 包含了 cache 需要的类。
定义实体类、服务类和相关配置文件
实体类就是上面自定义缓存方案定义的 Account.java,这里重新定义了服务类,如下:
清单 6. AccountService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
package cacheOfAnno;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
public class AccountService {
@Cacheable(value="accountCache")// 使用了一个缓存名叫 accountCache
public Account getAccountByName(String userName) {
// 方法内部实现不考虑缓存逻辑,直接实现业务
System.out.println("real query account."+userName);
return getFromDB(userName);
}
private Account getFromDB(String acctName) {
System.out.println("real querying db..."+acctName);
return new Account(acctName);
}
}
|
注意,此类的 getAccountByName 方法上有一个注释 annotation,即 @Cacheable(value=”accountCache”),这个注释的意思是,当调用这个方法的时候,会从一个名叫 accountCache 的缓存中查询,如果没有,则执行实际的方法(即查询数据库),并将执行的结果存入缓存中,否则返回缓存中的对象。这里的缓存中的 key 就是参数 userName,value 就是 Account 对象。“accountCache”缓存是在 spring*.xml 中定义的名称。
好,因为加入了 spring,所以我们还需要一个 spring 的配置文件来支持基于注释的缓存
清单 7. Spring-cache-anno.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
< beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:cache = "http://www.springframework.org/schema/cache"
xmlns:p = "http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
< cache:annotation-driven />
< bean id = "accountServiceBean" class = "cacheOfAnno.AccountService" />
<!-- generic cache manager -->
< bean id = "cacheManager"
class = "org.springframework.cache.support.SimpleCacheManager" >
< property name = "caches" >
< set >
< bean
class = "org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
p:name = "default" />
< bean
class = "org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
p:name = "accountCache" />
</ set >
</ property >
</ bean >
</ beans >
|
注意这个 spring 配置文件有一个关键的支持缓存的配置项:<cache:annotation-driven />,
这个配置项缺省使用了一个名字叫 cacheManager 的缓存管理器,这个缓存管理器有一个 spring 的缺省实现,即 org.springframework.cache.support.SimpleCacheManager,这个缓存管理器实现了我们刚刚自定义的缓存管理器的逻辑,它需要配置一个属性 caches,即此缓存管理器管理的缓存集合,除了缺省的名字叫 default 的缓存,我们还自定义了一个名字叫 accountCache 的缓存,使用了缺省的内存存储方案 ConcurrentMapCacheFactoryBean,它是基于 java.util.concurrent.ConcurrentHashMap 的一个内存缓存实现方案。
OK,现在我们具备了测试条件,测试代码如下:
清单 8. Main.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package cacheOfAnno;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"spring-cache-anno.xml");// 加载 spring 配置文件
AccountService s = (AccountService) context.getBean("accountServiceBean");
// 第一次查询,应该走数据库
System.out.print("first query...");
s.getAccountByName("somebody");
// 第二次查询,应该不查数据库,直接返回缓存的值
System.out.print("second query...");
s.getAccountByName("somebody");
System.out.println();
}
}
|
上面的测试代码主要进行了两次查询,第一次应该会查询数据库,第二次应该返回缓存,不再查数据库,我们执行一下,看看结果
清单 9. 执行结果
1
2
3
|
first query...real query account.somebody// 第一次查询
real querying db...somebody// 对数据库进行了查询
second query...// 第二次查询,没有打印数据库查询日志,直接返回了缓存中的结果
|
可以看出我们设置的基于注释的缓存起作用了,而在 AccountService.java 的代码中,我们没有看到任何的缓存逻辑代码,只有一行注释:@Cacheable(value="accountCache"),就实现了基本的缓存方案.
如何清空缓存
好,到目前为止,我们的 spring cache 缓存程序已经运行成功了,但是还不完美,因为还缺少一个重要的缓存管理逻辑:清空缓存,当账号数据发生变更,那么必须要清空某个缓存,另外还需要定期的清空所有缓存,以保证缓存数据的可靠性。
为了加入清空缓存的逻辑,我们只要对 AccountService.java 进行修改,从业务逻辑的角度上看,它有两个需要清空缓存的地方
- 当外部调用更新了账号,则我们需要更新此账号对应的缓存
- 当外部调用说明重新加载,则我们需要清空所有缓存
清单 10. AccountService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package cacheOfAnno;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
public class AccountService {
@Cacheable(value="accountCache")// 使用了一个缓存名叫 accountCache
public Account getAccountByName(String userName) {
// 方法内部实现不考虑缓存逻辑,直接实现业务
return getFromDB(userName);
}
@CacheEvict(value="accountCache",key="#account.getName()")// 清空 accountCache 缓存 public void updateAccount(Account account) {
updateDB(account);
}
@CacheEvict(value="accountCache",allEntries=true)// 清空 accountCache 缓存
public void reload() {
}
private Account getFromDB(String acctName) {
System.out.println("real querying db..."+acctName);
return new Account(acctName);
}
private void updateDB(Account account) {
System.out.println("real update db..."+account.getName());
}
}
|
清单 11. Main.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
package cacheOfAnno;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"spring-cache-anno.xml");// 加载 spring 配置文件
AccountService s = (AccountService) context.getBean("accountServiceBean");
// 第一次查询,应该走数据库
System.out.print("first query...");
s.getAccountByName("somebody");
// 第二次查询,应该不查数据库,直接返回缓存的值
System.out.print("second query...");
s.getAccountByName("somebody");
System.out.println();
System.out.println("start testing clear cache..."); // 更新某个记录的缓存,首先构造两个账号记录,然后记录到缓存中
Account account1 = s.getAccountByName("somebody1");
Account account2 = s.getAccountByName("somebody2");
// 开始更新其中一个 account1.setId(1212);
s.updateAccount(account1);
s.getAccountByName("somebody1");// 因为被更新了,所以会查询数据库 s.getAccountByName("somebody2");// 没有更新过,应该走缓存 s.getAccountByName("somebody1");// 再次查询,应该走缓存 // 更新所有缓存
s.reload();
s.getAccountByName("somebody1");// 应该会查询数据库 s.getAccountByName("somebody2");// 应该会查询数据库 s.getAccountByName("somebody1");// 应该走缓存 s.getAccountByName("somebody2");// 应该走缓存
}
}
|
清单 12. 运行结果
1
2
3
4
5
6
7
8
9
|
first query...real querying db...somebody
second query...
start testing clear cache...
real querying db...somebody1
real querying db...somebody2
real update db...somebody1
real querying db...somebody1
real querying db...somebody1
real querying db...somebody2
|
结果和我们期望的一致,所以,我们可以看出,spring cache 清空缓存的方法很简单,就是通过 @CacheEvict 注释来标记要清空缓存的方法,当这个方法被调用后,即会清空缓存。注意其中一个 @CacheEvict(value=”accountCache”,key=”#account.getName()”),其中的 Key 是用来指定缓存的 key 的,这里因为我们保存的时候用的是 account 对象的 name 字段,所以这里还需要从参数 account 对象中获取 name 的值来作为 key,前面的 # 号代表这是一个 SpEL 表达式,此表达式可以遍历方法的参数对象,具体语法可以参考 Spring 的相关文档手册。
springcache使用笔记001_注释驱动的 Spring cache 简单使用相关推荐
- springcache使用笔记003_注释驱动的 Spring cache 基本原理,注意和限制,@CacheEvict 的可靠性问题
基本原理 和 spring 的事务管理类似,spring cache 的关键原理就是 spring AOP,通过 spring AOP,其实现了在方法调用前.调用后获取方法的入参和返回值,进而实现了缓 ...
- springcache使用笔记002_注释驱动的 Spring cache 按条件查询
如何按照条件操作缓存 前面介绍的缓存方法,没有任何条件,即所有对 accountService 对象的 getAccountByName 方法的调用都会起动缓存效果,不管参数是什么值,如果有一个需求, ...
- 注释驱动的 Spring cache 缓存介绍--转载
概述 Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使 ...
- 注释驱动的 Spring cache 缓存介绍
概述 Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使 ...
- 注解驱动的 Spring cache 缓存介绍
概述 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术 ...
- spring boot 整合 spring cache 简单使用
spring boot 整合 spring cache 简单使用 spring cache简介 使用spring cache spring cache简介 Spring 3.1起,提供了基于注解的对C ...
- 学习笔记:cache 和spring cache 技术(1)
title: 学习笔记:cache 和spring cache 技术(1) author: Eric liu tags: [] categories: hexo 缓存是实际工作中非常常用的一种提高性能 ...
- 【SpringCache】Spring Cache详解
一.参考资料 Spring Cache_量子物理学的博客-CSDN博客_springcache Spring Cache简单介绍和使用 - zsychanpin - 博客园
- Spring Cache 介绍
缓存是实际工作中非常常用的一种提高性能的方法, 我们会在许多场景下来使用缓存. 本文通过一个简单的例子进行展开,通过对比我们原来的自定义缓存和 spring 的基于注释的 cache 配置方法,展现了 ...
最新文章
- (72)进程挂靠(attach)使用备用APC队列 SavedApcState 保存父进程 APC 队列,分析 NtReadVirtualMemory
- PLSQL Developer导入csv文件到oracle
- Xshell报错“The remote SSH server rejected X11 forwarding request.”
- war包部署到tomcat
- uni-app获取屏幕高度和宽度
- 微信小程序顶部tab切换
- 盒式滤波器BoxFilter
- 网页游戏服务器端开发心得
- 扎实干货!PP-Tracking:百度提出实时目标跟踪系统(附源码教程)
- ospf(MGRE的星型结构和全连结构)
- 流媒体协议(三):FLV协议
- UIkit框架之轮播特效
- android 6.0自启动管理器,安卓6.0技巧:系统自带文件管理器
- 简单学JAVA-学好Java能做啥
- 第六节 电子学习 二极管档测量法 细讲
- 中点画线完整算法c语言,中点画线算法
- Java Web项目源代码|CRM客户关系管理系统项目实战(Struts2+Spring+Hibernate)解析+源代码+教程
- 医学图像质量评价方法SSIM
- 阿里JAVA开发手册(泰山版)
- 服务应用执行可疑命令
热门文章
- python 近期用到的基础知识汇总(八)
- C语言编程数出1到100的整数中出现了多少次数字9
- Python零基础学习笔记(二十)—— tuple元组
- Python多线程学习(上)
- wkhtmltopdf网页转PDF程序安装教程
- c#自定义类型的转换方式operator,以及implicit(隐式)和explicit (显示)声明的区别...
- “安装程序无法定位现有系统分区,也无法创建新的系统分区”提示
- 在gridview中按钮传递data值到js中的方法(选择按钮)
- 流程多节点调用同一张业务表单的锚点问题
- vue中watch数组或者对象