需要使用 Spring 來實(shí)現(xiàn)一個(gè) Cache 簡(jiǎn)單的解決方案,具體需求如下:使用任意一個(gè)現(xiàn)有開源 Cache Framework,要求使用 Cache 系統(tǒng)中 Service 或則 DAO 層的 get/find 等方法返回結(jié)果,如果數(shù)據(jù)更新(使用 Create/update/delete 方法),則刷新 cache 中相應(yīng)的內(nèi)容。根據(jù)需求,計(jì)劃使用 Spring AOP + ehCache 來實(shí)現(xiàn)這個(gè)功能,采用 ehCache 原因之一是Spring 提供了 ehCache 的支持,至于為何僅僅支持 ehCache 而不支持 osCache 和JBossCache 無從得知(Hibernate???),但畢竟 Spring 提供了支持,可以減少一部分工作量:)。二是后來實(shí)現(xiàn)了 OSCache 和 JBoss Cache 的方式后,經(jīng)過簡(jiǎn)單測(cè)試發(fā)現(xiàn)幾個(gè) Cache 在效率上沒有太大的區(qū)別(不考慮集群),決定采用 ehCahce。AOP 嘛,少不了攔截器,先創(chuàng)建一個(gè)實(shí)現(xiàn)了 MethodInterceptor 接口的攔截器,用來攔截Service/DAO 的方法調(diào)用,攔截到方法后,搜索該方法的結(jié)果在 cache 中是否存在,如果存在,返回 cache 中的緩存結(jié)果,如果不存在,返回查詢數(shù)據(jù)庫的結(jié)果,并將結(jié)果緩存到 cache 中。
MethodCacheInterceptor.java
Java 代碼
package com.co.cache.ehcache;
import java.io.Serializable;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
public class MethodCacheInterceptor implements MethodInterceptor, InitializingBean
{
private static final Log logger = LogFactory.getLog(MethodCacheInterceptor.class);
private Cache cache;
public void setCache(Cache cache) {
this.cache = cache;
}
public MethodCacheInterceptor() {
super();
}
/**
* 攔截 Service/DAO 的方法,并查找該結(jié)果是否存在,如果存在就返回 cache 中的值,
* 否則,返回?cái)?shù)據(jù)庫查詢結(jié)果,并將查詢結(jié)果放入 cache
*/
public Object invoke(MethodInvocation invocation) throws Throwable {
String targetName = invocation.getThis().getClass().getName();
String methodName = invocation.getMethod().getName();
Object[] arguments = invocation.getArguments();
Object result;
logger.debug("Find object from cache is " + cache.getName());
String cacheKey = getCacheKey(targetName, methodName, arguments);
Element element = cache.get(cacheKey);
if (element == null) {
logger.debug("Hold up method , Get method result and create cache........!");
result = invocation.proceed();
element = new Element(cacheKey, (Serializable) result);
cache.put(element);
}
return element.getValue();
}
/**
* 獲得 cache key 的方法, cache key 是 Cache 中一個(gè) Element 的唯一標(biāo)識(shí)
* cache key 包括 包名 + 類名 + 方法名,如 com.co.cache.service.UserServiceImpl.getAllUser
*/
private String getCacheKey(String targetName, String methodName, Object[] arguments) {
StringBuffer sb = new StringBuffer();
sb.append(targetName).append(".").append(methodName);
if ((arguments != null) && (arguments.length != 0)) {
for (int i = 0; i < arguments.length; i++) {
sb.append(".").append(arguments[i]);
}
}
return sb.toString();
}
/**
* implement InitializingBean ,檢查 cache 是否為空
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(cache, "Need a cache. Please use setCache(Cache) create it.");
}
}
上面的代碼中可以看到,在方法 public Object invoke(MethodInvocation invocation) 中,完成了搜索Cache/ 新建 cache 的功能。
Element element = cache.get(cacheKey);
這句代碼的作用是獲取 cache 中的 element ,如果 cacheKey 所對(duì)應(yīng)的 element 不存在,將會(huì)返回一個(gè) null 值。
Java 代碼
result = invocation.proceed();
這句代碼的作用是獲取所攔截方法的返回值,詳細(xì)請(qǐng)查閱 AOP 相關(guān)文檔。隨后,再建立一個(gè)攔截器MethodCacheAfterAdvice ,作用是在用戶進(jìn)行 create/update/delete 操作時(shí)來刷新 /remove 相關(guān)cache 內(nèi)容,這個(gè)攔截器實(shí)現(xiàn)了 AfterReturningAdvice 接口,將會(huì)在所攔截的方法執(zhí)行后執(zhí)行在 public void afterReturning(Object arg0, Method arg1,Object[] arg2, Object arg3) 方法中所預(yù)定的操作
Java 代碼
package com.co.cache.ehcache;
import java.lang.reflect.Method;
import java.util.List;
import net.sf.ehcache.Cache;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
public class MethodCacheAfterAdvice implements AfterReturningAdvice, InitializingBean
{
private static final Log logger = LogFactory.getLog(MethodCacheAfterAdvice.class);
private Cache cache;
public void setCache(Cache cache) {
this.cache = cache;
}
public MethodCacheAfterAdvice() {
super();
}
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws
Throwable {
String className = arg3.getClass().getName();
List list = cache.getKeys();
for(int i = 0;i<list.size();i++){
String cacheKey = String.valueOf(list.get(i));
if(cacheKey.startsWith(className)){
cache.remove(cacheKey);
logger.debug("remove cache " + cacheKey);
}
}
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(cache, "Need a cache. Please use setCache(Cache) create it.");
}
}
上面的代碼很簡(jiǎn)單,實(shí)現(xiàn)了 afterReturning 方法實(shí)現(xiàn)自 AfterReturningAdvice 接口,方法中所定義的內(nèi)容將會(huì)在目標(biāo)方法執(zhí)行后執(zhí)行,在該方法中的作用是獲取目標(biāo) class 的全名,如:com.co.cache.test.TestServiceImpl ,然后循環(huán) cache 的 key list , remove cache 中所有和該 class 相關(guān)的 element 。
Java 代碼
String className = arg3.getClass().getName();
隨后,開始配置 ehCache 的屬性, ehCache 需要一個(gè) xml 文件來設(shè)置 ehCache 相關(guān)的一些屬性,如最大緩存數(shù)量、 cache 刷新的時(shí)間等等 .
ehcache.xml
<ehcache>
<diskStore path="c:\\myapp\\cache"/>
<defaultCache
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
<cache name="DEFAULT_CACHE"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300000"
timeToLiveSeconds="600000"
overflowToDisk="true"
/>
</ehcache>
配置每一項(xiàng)的詳細(xì)作用不再詳細(xì)解釋,有興趣的請(qǐng) google 下 ,這里需要注意一點(diǎn) defaultCache 標(biāo)簽定義了一個(gè)默認(rèn)的 Cache ,這個(gè) Cache 是不能刪除的,否則會(huì)拋出 No default cache is configured異常。另外,由于使用攔截器來刷新 Cache 內(nèi)容,因此在定義 cache 生命周期時(shí)可以定義較大的數(shù)值, timeToIdleSeconds="300000" , timeToLiveSeconds="600000" ,好像還不夠大?然后,在將Cache 和兩個(gè)攔截器配置到 Spring ,這里沒有使用 2.0 里面 AOP 的標(biāo)簽。
cacheContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd ">
<beans>
<!-- 引用 ehCache 的配置 -->
<bean id="defaultCacheManager"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation">
<value>ehcache.xml</value>
</property>
</bean>
<!-- 定義 ehCache 的工廠,并設(shè)置所使用的 Cache name -->
<bean id="ehCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager">
<ref local="defaultCacheManager"/>
</property>
<property name="cacheName">
<value>DEFAULT_CACHE</value>
</property>
</bean>
<!-- find/create cache 攔截器 -->
<bean id="methodCacheInterceptor"
class="com.co.cache.ehcache.MethodCacheInterceptor">
<property name="cache">
<ref local="ehCache" />
</property>
</bean>
<!-- flush cache 攔截器 -->
<bean id="methodCacheAfterAdvice"
class="com.co.cache.ehcache.MethodCacheAfterAdvice">
<property name="cache">
<ref local="ehCache" />
</property>
</bean>
<bean id="methodCachePointCut"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="methodCacheInterceptor"/>
</property>
<property name="patterns">
<list>
<value>.*find.*</value>
<value>.*get.*</value>
</list>
</property>
</bean>
<bean id="methodCachePointCutAdvice"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="methodCacheAfterAdvice"/>
</property>
<property name="patterns">
<list>
<value>.*create.*</value>
<value>.*update.*</value>
<value>.*delete.*</value>
</list>
</property>
</bean>
</beans>
上面的代碼最終創(chuàng)建了兩個(gè) " 切入點(diǎn) " , methodCachePointCut 和 methodCachePointCutAdvice ,分別用于攔截不同方法名的方法,可以根據(jù)需要任意增加所需要攔截方法的名稱。需要注意的是 Java代碼
<bean id="ehCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager">
<ref local="defaultCacheManager"/>
</property>
<property name="cacheName">
<value>DEFAULT_CACHE</value>
</property>
</bean>
如果 cacheName 屬性內(nèi)設(shè)置的 name 在 ehCache.xml 中無法找到,那么將使用默認(rèn)的
cache(defaultCache 標(biāo)簽定義 ) ,事實(shí)上到了這里,一個(gè)簡(jiǎn)單的 Spring + ehCache Framework 基本完成了,為了測(cè)試效果,舉一個(gè)實(shí)際應(yīng)用的例子,定義一個(gè) TestService 和它的實(shí)現(xiàn)類 TestServiceImpl ,里面包含兩個(gè)方法 getAllObject() 和 updateObject(Object Object) ,具體代碼如下:
TestService.java
Java 代碼
package com.co.cache.test;
import java.util.List;
public interface TestService {
public List getAllObject();
public void updateObject(Object Object);
}
TestServiceImpl.java
Java 代碼
package com.co.cache.test;
import java.util.List;
public class TestServiceImpl implements TestService
{
public List getAllObject() {
System.out.println("---TestService : Cache 內(nèi)不存在該 element ,查找并放入 Cache ! ");
return null;
}
public void updateObject(Object Object) {
System.out.println("---TestService :更新了對(duì)象,這個(gè) Class 產(chǎn)生的 cache 都將被 remove !
");
}
}
使用 Spring 提供的 AOP 進(jìn)行配置
applicationContext.xml
XML/HTML 代碼
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd ">
<beans>
<import resource="cacheContext.xml"/>
<bean id="testServiceTarget" class="com.co.cache.test.TestServiceImpl"/>
<bean id="testService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref local="testServiceTarget"/>
</property>
<property name="interceptorNames">
<list>
<value>methodCachePointCut</value>
<value>methodCachePointCutAdvice</value>
</list>
</property>
</bean>
</beans>
這里一定不能忘記 import cacheContext.xml 文件,不然定義的兩個(gè)攔截器就沒辦法使用
了。
最后,寫一個(gè)測(cè)試的代碼
MainTest.java
Java 代碼
(DEFAULT_CONTEXT_FILE);
TestService testService = (TestService)context.getBean("testService");
System.out.println("1-- 第一次查找并創(chuàng)建 cache");
testService.getAllObject();
System.out.println("2-- 在 cache 中查找 ");
testService.getAllObject();
System.out.println("3--remove cache");
testService.updateObject(null);
System.out.println("4-- 需要重新查找并創(chuàng)建 cache");
testService.getAllObject();
}
}
運(yùn)行,結(jié)果如下
Java 代碼
1-- 第一次查找并創(chuàng)建 cache
---TestService : Cache 內(nèi)不存在該 element ,查找并放入 Cache !
2-- 在 cache 中查找
3--remove cache
---TestService :更新了對(duì)象,這個(gè) Class 產(chǎn)生的 cache 都將被 remove !
4-- 需要重新查找并創(chuàng)建 cache
---TestService : Cache 內(nèi)不存在該 element ,查找并放入 Cache !
大功告成 . 可以看到,第一步執(zhí)行 getAllObject() ,執(zhí)行 TestServiceImpl 內(nèi)的方法,并創(chuàng)建了 cache,在第二次執(zhí)行 getAllObject() 方法時(shí),由于 cache 有該方法的緩存,直接從 cache 中 get 出方法的結(jié)果,所以沒有打印出 TestServiceImpl 中的內(nèi)容,而第三步,調(diào)用了 updateObject 方法,和TestServiceImpl 相關(guān)的 cache 被 remove ,所以在第四步執(zhí)行時(shí),又執(zhí)行 TestServiceImpl 中的方法,創(chuàng)建 Cache 。網(wǎng)上也有不少類似的例子,但是很多都不是很完備,自己參考了一些例子的代碼,其實(shí)在 spring-modules 中也提供了對(duì)幾種 cache 的支持, ehCache , OSCache , JBossCache 這些,看了一下,基本上都是采用類似的方式,只不過封裝的更完善一些,主要思路也還是 Spring 的 AOP ,有興趣的可以研究一下。