介绍 spring 3.1 激动人心的新特性:注释驱动的缓存,本文通过一个简单的例子进行展开,通过对比我们原来的自定义缓存和 spring 的基于注释的 cache 配置方法,展现了 spring cache 的强大之处,然后介绍了其基本的原理,扩展点和使用场景的限制。通过阅读本文,你可以短时间内掌握 spring 带来的强大缓存技术,在很少的配置下即可给既有代码提供缓存能力。
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;
}
}
然后定义一个缓存管理器,这个管理器负责实现缓存逻辑,支持对象的增加、修改和删除,支持值对象的泛型。如下:
package oldcache;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class MyCacheManager {
private Map cache =
new ConcurrentHashMap();
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();
}
}
package oldcache;
import cacheOfAnno.Account;
public class MyAccountService {
private MyCacheManager cacheManager;
public MyAccountService() {
cacheManager = new MyCacheManager();// 构造一个缓存管理器
}
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);
}
}
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");// 第二次查询,应该直接从缓存返回
}
}
real querying db...somebody// 第一次从数据库加载
get from cache...somebody// 第二次从缓存加载
after reload...// 清空缓存
real querying db...somebody// 又从数据库加载
get from cache...somebody// 从缓存加载
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);
}
}
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();
}
}
first query...real query account.somebody// 第一次查询
real querying db...somebody// 对数据库进行了查询
second query...// 第二次查询,没有打印数据库查询日志,直接返回了缓存中的结果
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());
}
}
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");// 应该走缓存
}
}
@Cacheable(value="accountCache",condition="#userName.length() <= 4")// 缓存名叫 accountCache
public Account getAccountByName(String userName) {
// 方法内部实现不考虑缓存逻辑,直接实现业务
return getFromDB(userName);
}
测试方法
s.getAccountByName("somebody");// 长度大于 4,不会被缓存
s.getAccountByName("sbd");// 长度小于 4,会被缓存
s.getAccountByName("somebody");// 还是查询数据库
s.getAccountByName("sbd");// 会从缓存返回
运行结果
real querying db...somebody
real querying db...sbd
real querying db...somebody
private String password;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Cacheable(value="accountCache",key="#userName.concat(#password)")
public Account getAccount(String userName,String password,boolean sendLog) {
// 方法内部实现不考虑缓存逻辑,直接实现业务
return getFromDB(userName,password);
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"spring-cache-anno.xml");// 加载 spring 配置文件
AccountService s = (AccountService) context.getBean("accountServiceBean");
s.getAccount("somebody", "123456", true);// 应该查询数据库
s.getAccount("somebody", "123456", true);// 应该走缓存
s.getAccount("somebody", "123456", false);// 应该走缓存
s.getAccount("somebody", "654321", true);// 应该查询数据库
s.getAccount("somebody", "654321", true);// 应该走缓存
}
@Cacheable(value="accountCache")// 使用了一个缓存名叫 accountCache
public Account getAccountByName(String userName) {
// 方法内部实现不考虑缓存逻辑,直接实现业务
return getFromDB(userName);
}
@CachePut(value="accountCache",key="#account.getName()")// 更新 accountCache 缓存
public Account updateAccount(Account account) {
return updateDB(account);
}
private Account updateDB(Account account) {
System.out.println("real updating db..."+account.getName());
return account;
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"spring-cache-anno.xml");// 加载 spring 配置文件
AccountService s = (AccountService) context.getBean("accountServiceBean");
Account account = s.getAccountByName("someone");
account.setPassword("123");
s.updateAccount(account);
account.setPassword("321");
s.updateAccount(account);
account = s.getAccountByName("someone");
System.out.println(account.getPassword());
}
value | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 | 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”} |
key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | 例如: @Cacheable(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 | 例如: @Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
value | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 | 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”} |
key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | 例如: @Cacheable(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 | 例如: @Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
value | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 | 例如: @CachEvict(value=”mycache”) 或者 @CachEvict(value={”cache1”,”cache2”} |
key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | 例如: @CachEvict(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才清空缓存 | 例如: @CachEvict(value=”testcache”, condition=”#userName.length()>2”) |
allEntries | 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 | 例如: @CachEvict(value=”testcache”,allEntries=true) |
beforeInvocation | 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 | 例如: @CachEvict(value=”testcache”,beforeInvocation=true) |
package cacheOfAnno;
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;
}
}
package cacheOfAnno;
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 store = new HashMap();;
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() {
}
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"spring-cache-anno.xml");// 加载 spring 配置文件
AccountService s = (AccountService) context.getBean("accountServiceBean");
Account account = s.getAccountByName("someone");
System.out.println("passwd="+account.getPassword());
account = s.getAccountByName("someone");
System.out.println("passwd="+account.getPassword());
}
public Account getAccountByName2(String userName) {
return this.getAccountByName(userName);
}
@Cacheable(value="accountCache")// 使用了一个缓存名叫 accountCache
public Account getAccountByName(String userName) {
// 方法内部实现不考虑缓存逻辑,直接实现业务
return getFromDB(userName);
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"spring-cache-anno.xml");// 加载 spring 配置文件
AccountService s = (AccountService) context.getBean("accountServiceBean");
s.getAccountByName2("someone");
s.getAccountByName2("someone");
s.getAccountByName2("someone");
}
@CacheEvict(value="accountCache",allEntries=true)// 清空 accountCache 缓存
public void reload() {
throw new RuntimeException();
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"spring-cache-anno.xml");// 加载 spring 配置文件
AccountService s = (AccountService) context.getBean("accountServiceBean");
s.getAccountByName("someone");
s.getAccountByName("someone");
try {
s.reload();
} catch (Exception e) {
}
s.getAccountByName("someone");
}
@CacheEvict(value="accountCache",allEntries=true,beforeInvocation=true)
// 清空 accountCache 缓存
public void reload() {
throw new RuntimeException();
}