利用Spring的AOP來配置和管理你的二級緩存(EHCache)

[b]利用Spring的AOP來配置和管理你的二級緩存(EHCache)[/b]
如果我們的項目中採用的是Spring+hibernate來構建的,在緩存方面,我們一定會首先想到Spring自帶的EHCache緩存工具,在 Spring中集成了目前比較流行的緩存策略EHCache,現在用的比較多的還有像OSCache,MemCached.這些應該是當前用的最多的緩存工具了。
在Spring+hibernate的這樣的框架中,EHCache應該屬於二級緩存了,我們知道在Hibernate中已經默認的使用了一級緩存,也就是在Session中。二級緩存應該是SessionFactory的範圍了。二級緩存默認不會起作用的,這就需要我們簡單的配置一下就可以了。
在配置之前,我先說明一點,緩存從理論上來說是可以提高你網站系統的性能,但前提就是你要保證你有一個良好的架構設計。比如用 Spring+Hibernate構建的系統,如果用單個服務器,用Spring自帶的EHCache來做二級緩存是再好不過了。如果你的系統是分佈式的系統,有多臺服務器,那麼MemCached是最好的選擇了,一般來說MemCached在做緩存這一塊,要比EHCache和OSCache的性能要好點,但是並不是所有的網站用MemCached都能達到事半功倍的,它雖然是比較好,但它有一個前提,那就是你有多臺服務器,是分佈式的。這樣用 MemCached對系統的性能一定OK。因爲Memcached是“分佈式”的內存對象緩存系統,那麼就是說,那些不需要“分佈”的,不需要共享的,或者乾脆規模小到只有一臺服務器的應用, MemCached不會帶來任何好處,相反還會拖慢系統效率,因爲網絡連接同樣需要資源 .OSCache這個緩存機制的限制就比較少了。它和EHCache差不多。
在Spring+Hibernate中整合EHCache只需簡單的三步。
第一步:配置緩存文件ehcache.xml,默認放到src目錄下。下面是簡單的配置。
<ehcache>
<!—設置緩存文件 .data 的創建路徑。
如果該路徑是 Java 系統參數,當前虛擬機會重新賦值。
下面的參數這樣解釋:
user.home – 用戶主目錄
user.dir – 用戶當前工作目錄
java.io.tmpdir – 默認臨時文件路徑,就是在tomcat的temp目錄 -->
<diskStore path="java.io.tmpdir"/>

<!—缺省緩存配置。CacheManager 會把這些配置應用到程序中。
下列屬性是 defaultCache 必須的:
maxInMemory - 設定內存中創建對象的最大值。
eternal - 設置元素(譯註:內存中對象)是否永久駐留。如果是,將忽略超
時限制且元素永不消亡。
timeToIdleSeconds - 設置某個元素消亡前的停頓時間。
也就是在一個元素消亡之前,兩次訪問時間的最大時間間隔值。
這只能在元素不是永久駐留時有效(譯註:如果對象永恆不滅,則
設置該屬性也無用)。
如果該值是 0 就意味着元素可以停頓無窮長的時間。
timeToLiveSeconds - 爲元素設置消亡前的生存時間。
也就是一個元素從構建到消亡的最大時間間隔值。
這只能在元素不是永久駐留時有效。
overflowToDisk - 設置當內存中緩存達到 maxInMemory 限制時元素是否可寫到磁盤
上。
-->
<!--timeToLiveSeconds的時間一定要大於等於timeToIdleSeconds的時間按-->
<cache name="DEFAULT_CACHE"
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="500"
timeToLiveSeconds="500"
overflowToDisk="true"
/>
</ehcache>
上面有一個默認的緩存配置,還有一個我們自己配置的緩存,在應用程序中如果不指明緩存的話,就會默認的使用默認的配置屬性。
第二步:用Spring中的強大機制,面向切面的設計AOP.來編寫兩個類文件,MethodCacheAfterAdvice.java(主要是對髒東西的同步更新)和MethodCacheInterceptor.java(主要使用攔截器來爲要緩存的對象建立緩存並緩存)。攔截器的實現機制其實就是我們常用的過濾器。它和過濾器的工作原理一樣。以下是這兩個文件。
MethodCacheInterceptor.java
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的方法,並查找該結果是否存在,如果存在就返回cache中的值, 否則,返回數據庫查詢結果,並將查詢結果放入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);
long startTime = System.currentTimeMillis();
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);
long endTime = System.currentTimeMillis();
logger.info(targetName + "." + methodName + " 方法被首次調用並被緩存。耗時"
+ (endTime - startTime) + "毫秒" + " cacheKey:"
+ element.getKey());
} else {
long endTime = System.currentTimeMillis();
logger.info(targetName + "." + methodName + " 結果從緩存中直接調用。耗時"
+ (endTime - startTime) + "毫秒" + " cacheKey:"
+ element.getKey());
}
return element.getValue();
}

/**
* 獲得cache key的方法,cache key是Cache中一個Element的唯一標識 cache key包括 包名+類名+方法名+參數
*/
 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.");
}
}

這個方法實現了兩個接口,一個是MethodInterceptor(方法攔截),它主要是在方法的調用前後都可以執行。另一個 InitializingBean (初始化Bean)它主要在方法調用之後做一下簡單的檢查,主要實現寫在afterPropertiesSet()中,就可以了 。
MethodCacheAfterAdvice .java
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.");
}
}

這個方法主要是保證緩存的同步,保持與數據庫的數據一致性。
第三步:配置Bean了,applicationContext-ehcache.xml文件就是Spring中的Ioc(控制反轉容器)的描述了。上面的只是簡單的寫了兩個方法,具體的能起到什麼作用,以及何時起作用,以及怎樣用聲明式的方式(AOP)和Bean結合。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="[url]http://www.springframework.org/schema/beans[/url]"
xmlns:xsi="[url]http://www.w3.org/2001/XMLSchema-instance[/url]"
xsi:schemaLocation="[url]http://www.springframework.org/schema/beans[/url] [url]http://www.springframework.org/schema/beans/spring-beans-2.0.xsd[/url]">
<!-- 利用BeanNameAutoProxyCreator自動創建事務代理 -->
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager">
<ref bean="transactionManager" />
</property>
<!-- 配置事務屬性 -->
<property name="transactionAttributes">
<props>
<prop key="delete*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="save*">PROPAGATION_REQUIRED</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>

<!-- 引用ehCache的配置 -->
<bean id="defaultCacheManager"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation">
<value>classpath:ehcache.xml</value>
</property>
</bean>
<!-- 定義ehCache的工廠,並設置所使用的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.w3cs.cache.ehcache.MethodCacheInterceptor">
<property name="cache">
<ref local="ehCache" />
</property>
</bean>
<!-- flush cache攔截器 -->
<bean id="methodCacheAfterAdvice"
class="com.w3cs.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>
<!-- 自動代理 -->
<bean id="autoproxy"
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<!-- 可以是Service或DAO層(最好是針對業務層*Service) -->
<property name="beanNames">
<list>
<value>*DAO</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>methodCachePointCut</value>
<value>methodCachePointCutAdvice</value>
<value>transactionInterceptor</value>
</list>
</property>
</bean>
</beans>

上面我是針對DAO層進行攔截並緩存的,最好是能在業務層進行攔截會更好,你可以根據你的系統具體的設計,如果沒有業務層的話,對DAO層攔截也是可以的。攔截採用的是用正規表達式配置的。對find,get的方法只進行緩存,如果 create,update,delete方法進行緩存的同步。對一些頻繁的操作最好不要用緩存,緩存的作用就是針對那些不經常變動的操作。
只需這簡單的三部就可以完成EHCache了。最好親自試一試。我並沒有針對裏面對方法過細的講解。其實都很簡單,多看看就會明白了。不當之處,敬請原諒。
發佈了45 篇原創文章 · 獲贊 0 · 訪問量 2400
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章