介绍 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<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(); } }
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); } }
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); } }
<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>
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<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() { } }
<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="cacheOfAnno.MyCacheManager"> <property name="caches"> <set> <bean class="cacheOfAnno.MyCache" p:name="accountCache" /> </set> </property> </bean> </beans>
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(); }
<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="simpleCacheManager" class="org.springframework.cache.support.SimpleCacheManager"> <property name="caches"> <set> <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default" /> </set> </property> </bean> <!-- dummy cacheManager --> <bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager"> <property name="cacheManagers"> <list> <ref bean="simpleCacheManager" /> </list> </property> <property name="fallbackToNoOpCache" value="true" /> </bean> </beans>