Ehcache 整合Spring 使用對象緩存、頁面緩存

一:Ehcache瞭解

Ehcache在很多項目中都出現過,用法也比較簡單。一般的加些配置就可以了,而且Ehcache可以對頁面、對象、數據進行緩存,同時支持集羣/分佈式緩存。

如果整合Spring、Hibernate也非常的簡單,Spring對Ehcache的支持也非常好。EHCache支持內存和磁盤的緩存,支持LRU、LFU和FIFO多種淘汰算法,支持分式的Cache,可以作爲Hibernate的緩存插件。同時它也能提供基於Filter的Cache,該Filter可以緩存響應的內容並採用Gzip壓縮提高響應速度

二:EhCache 的主要特性有:
1. 快速、精幹;
2. 簡單;
3. 多種緩存策略;
4. 緩存數據有兩級:內存和磁盤,因此無需擔心容量問題;
5. 緩存數據會在虛擬機重啓的過程中寫入磁盤;
6. 可以通過 RMI、可插入 API 等方式進行分佈式緩存;
7. 具有緩存和緩存管理器的偵聽接口;
8. 支持多緩存管理器實例,以及一個實例的多個緩存區域;
9. 提供 Hibernate 的緩存實現;


三:具體使用

1、緩存的切面放在哪一層最合適(大部分情況是service,dao),其實應用在哪一層都有各自的用武之地,如:
(1)放在service,是緩存整個經過業務處理後的一個結果,這樣的做法大大減少了系統邏輯處理,但如果業務方法裏面涉及到多表操作,則比較麻煩,因爲   要考慮緩存數據更新的問題。
(2)放在dao,大多數都放在這層,也是推薦放在這層,因爲基本不用人爲的考慮緩存及時更新的問題造成業務方法返回的結果不一致。只緩存數據,不牽扯   到業務邏輯
2、對於某些特殊情況下的方法,不需要緩存,或者不需要更新緩存的方法,通過參數排除
3、考慮需要緩存更新,所以需要兩個攔截器去攔截不同的方法,做不同的處理
(1)緩存攔截器,在方法調用之前攔截,如(find,query,select,get方法),經過一些邏輯處理,再判斷返回緩存還是真實的數據

(2)更新緩存攔截器,在方法調用之後,如(save,insert,update,delete方法) 


四:頁面緩存:

對象緩存就是將查詢的數據,添加到緩存中,下次再次查詢的時候直接從緩存中獲取,而不去數據庫中查詢。

對象緩存一般是針對方法、類而來的,結合Spring的Aop對象、方法緩存就很簡單。這裏需要用到切面編程,用到了Spring的MethodInterceptor或是用@Aspect。

package com.voole.lzw.common.cache;

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;

/**
 * @Description: 緩存方法攔截器核心代碼 
 * @author lzw 
 * @date 2013年10月25日 下午4:34:44
 * @version V1.0
 * @Copyright (c)
 */
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;
	}
	
	/**
	 * @Description: 檢驗緩存是否爲空
	 * @throws Exception
	 */
	@Override
	public void afterPropertiesSet() throws Exception {
		Assert.notNull(cache, "Need a cache. Please use setCache(Cache) create it!");
	}
    
	/**
	 * @Description: 攔截切入點中的方法,如果存在該結果,則返回cache中的值,
     * 否則,則從數據庫中查詢返回並放入cache中 
	 * @param invocation
	 * @throws Throwable
	 */
	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		//獲取類名
		String targetName  = invocation.getThis().getClass().getName();
		//獲取方法名
        String methodName  = invocation.getMethod().getName();
        //獲取方法裏的參數
        Object[] arguments = null;
        Object result = null;
        String cacheKey = getCacheKey(targetName, methodName, arguments);
        Element element = null;
        synchronized(this) {
        	element = cache.get(cacheKey);
        	if (element == null) {
    			//執行完方法的返回值:調用proceed()方法,就會觸發切入點方法執行
    			result = invocation.proceed();
    			logger.info("第一次調用方法並緩存其值:" + result);
    			element = new Element(cacheKey, (Serializable) result);
    			cache.put(element);
    		} else {
    			logger.info("在緩存獲取其值:" + element.getValue());
    			result = element.getValue();
    		}
        }
		return result;
	}
	
	/**
	 * @Description: 返回具體的方法全路徑名稱參數
	 * @param targetName 類名
	 * @param methodName 方法名
	 * @param arguments  參數
	 * @return 緩存的Key值(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();
	}
}

package com.voole.lzw.common.cache;

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;
	}
    
	/**
	 * @Description: 清除緩存(在目標方法執行之後,執行該方法)
	 * @throws Throwable
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
		String className = target.getClass().getName();  
        List<String> cacheKeys = cache.getKeys();
        logger.info("[清除緩存:]" + cacheKeys);
        for (String cacheKey : cacheKeys) {  
            if(cacheKey.startsWith(className)){  
                cache.remove(cacheKey);
            }  
        }  
	}
	
	@Override
	public void afterPropertiesSet() throws Exception {
		Assert.notNull(cache, "Need a cache. Please use setCache(Cache) create it.");
	}

}

這裏的方法攔截器主要是對你要攔截的類的方法進行攔截,然後判斷該方法的類路徑+方法名稱+參數值組合的cache key在緩存cache中是否存在。
如果存在就從緩存中取出該對象,轉換成我們要的返回類型。沒有的話就把該方法返回的對象添加到緩存中即可。
值得主意的是當前方法的參數和返回值的對象類型需要序列化,需要在src目錄下添加cacheContext.xml完成對MethodCacheInterceptor攔截器的配置,
該配置主意是注入我們的cache對象,哪個cache來管理對象緩存,然後哪些類、方法參與該攔截器的掃描。

五:頁面緩存


package com.voole.lzw.common.cache;

import java.util.Arrays;
import java.util.Enumeration;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.ehcache.CacheException;
import net.sf.ehcache.constructs.blocking.LockTimeoutException;
import net.sf.ehcache.constructs.web.AlreadyCommittedException;
import net.sf.ehcache.constructs.web.AlreadyGzippedException;
import net.sf.ehcache.constructs.web.filter.FilterNonReentrantException;
import net.sf.ehcache.constructs.web.filter.SimplePageCachingFilter;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

/**
 * @Description: 頁面緩存過濾器
 * @author lzw
 * @date 2013年10月25日 下午4:34:59
 * @version V1.0
 * @Copyright (c)
 */
public class PageEhCacheFilter extends SimplePageCachingFilter {

    private final static Logger log = Logger.getLogger(PageEhCacheFilter.class);
    private final static String FILTER_URL_PATTERNS = "patterns";
    private static String[] cacheURLs;

    private void init() throws CacheException {
        String patterns = filterConfig.getInitParameter(FILTER_URL_PATTERNS);
        cacheURLs = StringUtils.split(patterns, ",");
    }

    @Override
	protected void doFilter(final HttpServletRequest request,
			final HttpServletResponse response, final FilterChain chain)
			throws AlreadyGzippedException, AlreadyCommittedException,
			FilterNonReentrantException, LockTimeoutException, Exception {
		if (cacheURLs == null) {
			init();
		}
		String url = request.getRequestURI();
		log.info("請求路徑:"+ url);
		log.info("過濾路徑:"+ Arrays.toString(cacheURLs));	
		boolean flag = true;
		if (cacheURLs != null && cacheURLs.length > 0) {
			for (String cacheURL : cacheURLs) {
				if (url.contains(cacheURL.trim())) {
					flag = true;
					break;
				}
			}
		}
		// 如果包含我們要緩存的url 就緩存該頁面,否則執行正常的頁面轉向
		if (flag) {
			String query = request.getQueryString();
			if (query != null) {
				query = "?" + query;
			}
			log.info("當前請求被緩存:" + url + " == " + query);
			super.doFilter(request, response, chain);
		} else {
			chain.doFilter(request, response);
		}
	}

	private boolean headerContains(final HttpServletRequest request, final String header, final String value) {
        logRequestHeaders(request);
		final Enumeration<String> accepted = request.getHeaders(header);
        while (accepted.hasMoreElements()) {
            final String headerValue = (String) accepted.nextElement();
            if (headerValue.indexOf(value) != -1) {
                return true;
            }
        }
        return false;
    }
    
    @Override
    protected boolean acceptsGzipEncoding(HttpServletRequest request) {
    	//兼容ie6/7 gzip壓縮
        boolean ie6 = headerContains(request, "User-Agent", "MSIE 6.0");
        boolean ie7 = headerContains(request, "User-Agent", "MSIE 7.0");
        return acceptsEncoding(request, "gzip") || ie6 || ie7;
    }
}

PageEhCacheFilter繼承了SimplePageCachingFilter,一般情況下SimplePageCachingFilter就夠用了,這裏是爲了滿足當前系統需求才做了覆蓋操作。

使用SimplePageCachingFilter需要在web.xml中配置cacheName,cacheName默認是SimplePageCachingFilter,對應ehcache.xml中的cache配置,否則會報錯。


web.xml 需要加入:


<!-- 緩存、gzip壓縮核心過濾器 -->
	<filter>
		<filter-name>PageEhCacheFilter</filter-name>
		<filter-class>com.voole.lzw.common.cache.PageEhCacheFilter</filter-class>
		<init-param>
			<param-name>patterns</param-name>
			<!-- 配置你需要緩存的url -->
			<param-value>/jsp/*</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>PageEhCacheFilter</filter-name>
		<url-pattern>*.action</url-pattern>
	</filter-mapping>
	<filter-mapping>
		<filter-name>PageEhCacheFilter</filter-name>
		<url-pattern>*.jsp</url-pattern>
	</filter-mapping>

cacheContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
	
    <!-- 配置ehcahe緩存管理器 -->
	<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
		<property name="configLocation" value="classpath:ehcache.xml" />
	</bean>
    
    <!-- 配置緩存工廠bean對象 -->
	<bean id="ehCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
		<property name="cacheManager" ref="cacheManager" />
		<property name="cacheName" value="lzw" />
	</bean>
    
    <!-- 配置一個緩存攔截器對象,處理具體的緩存業務 -->
	<bean id="methodICachenterceptor" class="com.voole.lzw.common.cache.MethodCacheInterceptor">
		<property name="cache" ref="ehCache" />
	</bean>
	
	<bean id="methodCacheAfterAdvice" class="com.voole.lzw.common.cache.MethodCacheAfterAdvice">
		<property name="cache" ref="ehCache" />
	</bean>
    
    <!-- 參與緩存的切入點對象 (切入點對象,調用攔截器) -->
	<bean id="methodCachePointCutAdvice" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
	    <!-- 配置緩存aop切面 -->
	    <property name="advice" ref="methodICachenterceptor" /> 
	    <!-- 配置哪些方法參與緩存策略 --> 
	    <property name="patterns" >  
	        <list>
	            <value>com.voole.lzw.service.*.Service.*.find.*</value>
	        </list>
	    </property>  
    </bean>
    
    <bean id="methodCachePointCutAfterAdvice" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">  
	    <property name="advice" ref="methodCacheAfterAdvice" />  
	    <property name="patterns" >  
	        <list>
	            <value>com.voole.lzw.service.*.Service.*.update.*</value>
	        </list>
	    </property>  
    </bean>
    
</beans>

ehcache.xml:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true"
	monitoring="autodetect">
 <!-- 
		diskStore :指定數據存儲位置,可指定磁盤中的文件夾位置
		defaultCache : 默認的管理策略
		
		以下屬性是必須的:
			name: Cache的名稱,必須是唯一的(ehcache會把這個cache放到HashMap裏)。maxElementsInMemory:在內存中緩存的element的最大數目。 
			maxElementsOnDisk:在磁盤上緩存的element的最大數目,默認值爲0,表示不限制。 
			eternal:設定緩存的elements是否永遠不過期。如果爲true,則緩存的數據始終有效,如果爲false那麼還要根據timeToIdleSeconds,timeToLiveSeconds判斷。 
			overflowToDisk: 如果內存中數據超過內存限制,是否要緩存到磁盤上。 
			
		以下屬性是可選的:
			timeToIdleSeconds: 對象空閒時間,指對象在多長時間沒有被訪問就會失效。只對eternal爲false的有效。默認值0,表示一直可以訪問。
			timeToLiveSeconds: 對象存活時間,指對象從創建到失效所需要的時間。只對eternal爲false的有效。默認值0,表示一直可以訪問。
			diskPersistent: 是否在磁盤上持久化。指重啓jvm後,數據是否有效。默認爲false。 
			diskExpiryThreadIntervalSeconds: 對象檢測線程運行時間間隔。標識對象狀態的線程多長時間運行一次。 
			diskSpoolBufferSizeMB: DiskStore使用的磁盤大小,默認值30MB。每個cache使用各自的DiskStore。 
			memoryStoreEvictionPolicy: 如果內存中數據超過內存限制,向磁盤緩存時的策略。默認值LRU,可選FIFO、LFU。 
		
			緩存的3 種清空策略 :
			FIFO ,first in first out (先進先出).
			LFU , Less Frequently Used (最少使用).意思是一直以來最少被使用的。緩存的元素有一個hit 屬性,hit 值最小的將會被清出緩存。
			LRU ,Least Recently Used(最近最少使用). (ehcache 默認值).緩存的元素有一個時間戳,當緩存容量滿了,而又需要騰出地方來緩存新的元素的時候,那麼現有緩存元素中時間戳離當前時間最遠的元素將被清出緩存。
-->
	<!--緩存路徑-->
	<diskStore path="E:/cachetmpdir"/>
	<defaultCache maxElementsInMemory="10000" eternal="true"
		timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true"
		maxElementsOnDisk="10000000" diskPersistent="false"
		diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" />
	
		
	<cache name="lzw" maxElementsInMemory="10000"
		maxElementsOnDisk="1000" eternal="false" overflowToDisk="true"
		diskSpoolBufferSizeMB="20" timeToIdleSeconds="300" timeToLiveSeconds="600"
		memoryStoreEvictionPolicy="LFU" />
		
	<cache name="SimplePageCachingFilter" maxElementsInMemory="10000"
		maxElementsOnDisk="1000" eternal="false" overflowToDisk="true"
		diskSpoolBufferSizeMB="20" timeToIdleSeconds="300" timeToLiveSeconds="600"
		memoryStoreEvictionPolicy="LFU" />
</ehcache>  

applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
	xmlns:jee="http://www.springframework.org/schema/jee" xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:p="http://www.springframework.org/schema/p" xmlns:cache="http://www.springframework.org/schema/cache"
	xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
    http://www.springframework.org/schema/jee
    http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.2.xsd
    http://www.springframework.org/schema/util 
    http://www.springframework.org/schema/util/spring-util-3.2.xsd
    http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.2.xsd">
    
    <import resource="cacheContext.xml"/>
	
	<!-- 開啓事務控制的註解支持 -->
	<tx:annotation-driven />
	<context:component-scan base-package="com.voole.lzw" />
	<context:annotation-config />
	<aop:aspectj-autoproxy proxy-target-class="true" />
		
</beans>

轉載地址:http://blog.csdn.net/lzwjavaphp/article/details/13767019

最近整理了學習材料,有需要的請下載,我放微信裏面了,方便下載,還能交流,掃描我的二維碼頭像即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章