Spring中集成Ehcache使用頁(yè)面、對(duì)象緩存
Ehcache在很多項(xiàng)目中都出現(xiàn)過,用法也比較簡(jiǎn)單。一般的加些配置就可以了,而且Ehcache可以對(duì)頁(yè)面、對(duì)象、數(shù)據(jù)進(jìn)行緩存,同時(shí)支持集群/分布式緩存。如果整合Spring、Hibernate也非常的簡(jiǎn)單,Spring對(duì)Ehcache的支持也非常好。EHCache支持內(nèi)存和磁盤的緩存,支持LRU、LFU和FIFO多種淘汰算法,支持分布式的Cache,可以作為Hibernate的緩存插件。同時(shí)它也能提供基于Filter的Cache,該Filter可以緩存響應(yīng)的內(nèi)容并采用Gzip壓縮提高響應(yīng)速度。
Email:hoojo_@126.com
Blog:http://blog.csdn.net/IBM_hoojo
一、準(zhǔn)備工作
如果你的系統(tǒng)中已經(jīng)成功加入Spring、Hibernate;那么你就可以進(jìn)入下面Ehcache的準(zhǔn)備工作。
1、 下載jar包
Ehcache 對(duì)象、數(shù)據(jù)緩存:http://ehcache.org/downloads/destination?name=ehcache-core-2.5.2-distribution.tar.gz&bucket=tcdistributions&file=ehcache-core-2.5.2-distribution.tar.gz
2、 需要添加如下jar包到lib目錄下
ehcache-core-2.5.2.jar
ehcache-web-2.0.4.jar 主要針對(duì)頁(yè)面緩存 |
3、 當(dāng)前工程的src目錄中加入配置文件
ehcache.xml
ehcache.xsd |
這些配置文件在ehcache-core這個(gè)jar包中可以找到
二、Ehcache基本用法
- CacheManager cacheManager = CacheManager.create();
- // 或者
- cacheManager = CacheManager.getInstance();
- // 或者
- cacheManager = CacheManager.create("/config/ehcache.xml");
- // 或者
- cacheManager = CacheManager.create("http://localhost:8080/test/ehcache.xml");
- cacheManager = CacheManager.newInstance("/config/ehcache.xml");
- // .......
- // 獲取ehcache配置文件中的一個(gè)cache
- Cache sample = cacheManager.getCache("sample");
- // 獲取頁(yè)面緩存
- BlockingCache cache = new BlockingCache(cacheManager.getEhcache("SimplePageCachingFilter"));
- // 添加數(shù)據(jù)到緩存中
- Element element = new Element("key", "val");
- sample.put(element);
- // 獲取緩存中的對(duì)象,注意添加到cache中對(duì)象要序列化 實(shí)現(xiàn)Serializable接口
- Element result = sample.get("key");
- // 刪除緩存
- sample.remove("key");
- sample.removeAll();
- // 獲取緩存管理器中的緩存配置名稱
- for (String cacheName : cacheManager.getCacheNames()) {
- System.out.println(cacheName);
- }
- // 獲取所有的緩存對(duì)象
- for (Object key : cache.getKeys()) {
- System.out.println(key);
- }
- // 得到緩存中的對(duì)象數(shù)
- cache.getSize();
- // 得到緩存對(duì)象占用內(nèi)存的大小
- cache.getMemoryStoreSize();
- // 得到緩存讀取的命中次數(shù)
- cache.getStatistics().getCacheHits();
- // 得到緩存讀取的錯(cuò)失次數(shù)
- cache.getStatistics().getCacheMisses();
三、頁(yè)面緩存
頁(yè)面緩存主要用Filter過濾器對(duì)請(qǐng)求的url進(jìn)行過濾,如果該url在緩存中出現(xiàn)。那么頁(yè)面數(shù)據(jù)就從緩存對(duì)象中獲取,并以gzip壓縮后返回。其速度是沒有壓縮緩存時(shí)速度的3-5倍,效率相當(dāng)之高!其中頁(yè)面緩存的過濾器有CachingFilter,一般要擴(kuò)展filter或是自定義Filter都繼承該CachingFilter。
CachingFilter功能可以對(duì)HTTP響應(yīng)的內(nèi)容進(jìn)行緩存。這種方式緩存數(shù)據(jù)的粒度比較粗,例如緩存整張頁(yè)面。它的優(yōu)點(diǎn)是使用簡(jiǎn)單、效率高,缺點(diǎn)是不夠靈活,可重用程度不高。
EHCache使用SimplePageCachingFilter類實(shí)現(xiàn)Filter緩存。該類繼承自CachingFilter,有默認(rèn)產(chǎn)生cache key的calculateKey()方法,該方法使用HTTP請(qǐng)求的URI和查詢條件來(lái)組成key。也可以自己實(shí)現(xiàn)一個(gè)Filter,同樣繼承CachingFilter類,然后覆寫calculateKey()方法,生成自定義的key。
CachingFilter輸出的數(shù)據(jù)會(huì)根據(jù)瀏覽器發(fā)送的Accept-Encoding頭信息進(jìn)行Gzip壓縮。
在使用Gzip壓縮時(shí),需注意兩個(gè)問題:
1. Filter在進(jìn)行Gzip壓縮時(shí),采用系統(tǒng)默認(rèn)編碼,對(duì)于使用GBK編碼的中文網(wǎng)頁(yè)來(lái)說(shuō),需要將操作系統(tǒng)的語(yǔ)言設(shè)置為:zh_CN.GBK,否則會(huì)出現(xiàn)亂碼的問題。
2. 默認(rèn)情況下CachingFilter會(huì)根據(jù)瀏覽器發(fā)送的請(qǐng)求頭部所包含的Accept-Encoding參數(shù)值來(lái)判斷是否進(jìn)行Gzip壓縮。雖然IE6/7瀏覽器是支持Gzip壓縮的,但是在發(fā)送請(qǐng)求的時(shí)候卻不帶該參數(shù)。為了對(duì)IE6/7也能進(jìn)行Gzip壓縮,可以通過繼承CachingFilter,實(shí)現(xiàn)自己的Filter,然后在具體的實(shí)現(xiàn)中覆寫方法acceptsGzipEncoding。
具體實(shí)現(xiàn)參考:
- protected boolean acceptsGzipEncoding(HttpServletRequest request) {
- boolean ie6 = headerContains(request, "User-Agent", "MSIE 6.0");
- boolean ie7 = headerContains(request, "User-Agent", "MSIE 7.0");
- return acceptsEncoding(request, "gzip") || ie6 || ie7;
- }
在ehcache.xml中加入如下配置
- <?xml version="1.0" encoding="gbk"?>
- <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd">
- <diskStore path="java.io.tmpdir"/>
- <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="30" timeToLiveSeconds="30" overflowToDisk="false"/>
- <!--
- 配置自定義緩存
- maxElementsInMemory:緩存中允許創(chuàng)建的***對(duì)象數(shù)
- eternal:緩存中對(duì)象是否為***的,如果是,超時(shí)設(shè)置將被忽略,對(duì)象從不過期。
- timeToIdleSeconds:緩存數(shù)據(jù)的鈍化時(shí)間,也就是在一個(gè)元素消亡之前,
- 兩次訪問時(shí)間的***時(shí)間間隔值,這只能在元素不是***駐留時(shí)有效,
- 如果該值是 0 就意味著元素可以停頓無(wú)窮長(zhǎng)的時(shí)間。
- timeToLiveSeconds:緩存數(shù)據(jù)的生存時(shí)間,也就是一個(gè)元素從構(gòu)建到消亡的***時(shí)間間隔值,
- 這只能在元素不是***駐留時(shí)有效,如果該值是0就意味著元素可以停頓無(wú)窮長(zhǎng)的時(shí)間。
- overflowToDisk:內(nèi)存不足時(shí),是否啟用磁盤緩存。
- memoryStoreEvictionPolicy:緩存滿了之后的淘汰算法。
- -->
- <cache name="SimplePageCachingFilter"
- maxElementsInMemory="10000"
- eternal="false"
- overflowToDisk="false"
- timeToIdleSeconds="900"
- timeToLiveSeconds="1800"
- memoryStoreEvictionPolicy="LFU" />
- </ehcache>
具體代碼:
- package com.hoo.ehcache.filter;
- 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;
- /**
- * <b>function:</b> mobile 頁(yè)面緩存過濾器
- * @author hoojo
- * @createDate 2012-7-4 上午09:34:30
- * @file PageEhCacheFilter.java
- * @package com.hoo.ehcache.filter
- * @project Ehcache
- * @blog http://blog.csdn.net/IBM_hoojo
- * @email hoojo_@126.com
- * @version 1.0
- */
- 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();
- boolean flag = false;
- if (cacheURLs != null && cacheURLs.length > 0) {
- for (String cacheURL : cacheURLs) {
- if (url.contains(cacheURL.trim())) {
- flag = true;
- break;
- }
- }
- }
- // 如果包含我們要緩存的url 就緩存該頁(yè)面,否則執(zhí)行正常的頁(yè)面轉(zhuǎn)向
- if (flag) {
- String query = request.getQueryString();
- if (query != null) {
- query = "?" + query;
- }
- log.info("當(dāng)前請(qǐng)求被緩存:" + url + query);
- super.doFilter(request, response, chain);
- } else {
- chain.doFilter(request, response);
- }
- }
- @SuppressWarnings("unchecked")
- private boolean headerContains(final HttpServletRequest request, final String header, final String value) {
- logRequestHeaders(request);
- final Enumeration accepted = request.getHeaders(header);
- while (accepted.hasMoreElements()) {
- final String headerValue = (String) accepted.nextElement();
- if (headerValue.indexOf(value) != -1) {
- return true;
- }
- }
- return false;
- }
- /**
- * @see net.sf.ehcache.constructs.web.filter.Filter#acceptsGzipEncoding(javax.servlet.http.HttpServletRequest)
- * <b>function:</b> 兼容ie6/7 gzip壓縮
- * @author hoojo
- * @createDate 2012-7-4 上午11:07:11
- */
- @Override
- protected boolean acceptsGzipEncoding(HttpServletRequest request) {
- 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就夠用了,這里是為了滿足當(dāng)前系統(tǒng)需求才做了覆蓋操作。使用SimplePageCachingFilter需要在web.xml中配置cacheName,cacheName默認(rèn)是SimplePageCachingFilter,對(duì)應(yīng)ehcache.xml中的cache配置。
- 在web.xml中加入如下配置
- <!-- 緩存、gzip壓縮核心過濾器 -->
- <filter>
- <filter-name>PageEhCacheFilter</filter-name>
- <filter-class>com.hoo.ehcache.filter.PageEhCacheFilter</filter-class>
- <init-param>
- <param-name>patterns</param-name>
- <!-- 配置你需要緩存的url -->
- <param-value>/cache.jsp, product.action, market.action </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>
- 當(dāng)***次請(qǐng)求這些頁(yè)面后,這些頁(yè)面就會(huì)被添加到緩存中,以后請(qǐng)求這些頁(yè)面將會(huì)從緩存中獲取。你可以在cache.jsp頁(yè)面中用小腳本來(lái)測(cè)試該頁(yè)面是否被緩存。<%=new Date()%>如果時(shí)間是變動(dòng)的,則表示該頁(yè)面沒有被緩存或是緩存已經(jīng)過期,否則則是在緩存狀態(tài)了。
- 四、對(duì)象緩存
- 對(duì)象緩存就是將查詢的數(shù)據(jù),添加到緩存中,下次再次查詢的時(shí)候直接從緩存中獲取,而不去數(shù)據(jù)庫(kù)中查詢。
- 對(duì)象緩存一般是針對(duì)方法、類而來(lái)的,結(jié)合Spring的Aop對(duì)象、方法緩存就很簡(jiǎn)單。這里需要用到切面編程,用到了Spring的MethodInterceptor或是用@Aspect。
- 代碼如下:
- package com.hoo.common.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.log4j.Logger;
- import org.springframework.beans.factory.InitializingBean;
- /**
- * <b>function:</b> 緩存方法攔截器核心代碼
- * @author hoojo
- * @createDate 2012-7-2 下午06:05:34
- * @file MethodCacheInterceptor.java
- * @package com.hoo.common.ehcache
- * @project Ehcache
- * @blog http://blog.csdn.net/IBM_hoojo
- * @email hoojo_@126.com
- * @version 1.0
- */
- public class MethodCacheInterceptor implements MethodInterceptor, InitializingBean {
- private static final Logger log = Logger.getLogger(MethodCacheInterceptor.class);
- private Cache cache;
- public void setCache(Cache cache) {
- this.cache = cache;
- }
- public void afterPropertiesSet() throws Exception {
- log.info(cache + " A cache is required. Use setCache(Cache) to provide one.");
- }
- public Object invoke(MethodInvocation invocation) throws Throwable {
- String targetName = invocation.getThis().getClass().getName();
- String methodName = invocation.getMethod().getName();
- Object[] arguments = invocation.getArguments();
- Object result;
- String cacheKey = getCacheKey(targetName, methodName, arguments);
- Element element = null;
- synchronized (this) {
- element = cache.get(cacheKey);
- if (element == null) {
- log.info(cacheKey + "加入到緩存: " + cache.getName());
- // 調(diào)用實(shí)際的方法
- result = invocation.proceed();
- element = new Element(cacheKey, (Serializable) result);
- cache.put(element);
- } else {
- log.info(cacheKey + "使用緩存: " + cache.getName());
- }
- }
- return element.getValue();
- }
- /**
- * <b>function:</b> 返回具體的方法全路徑名稱 參數(shù)
- * @author hoojo
- * @createDate 2012-7-2 下午06:12:39
- * @param targetName 全路徑
- * @param methodName 方法名稱
- * @param arguments 參數(shù)
- * @return 完整方法名稱
- */
- 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();
- }
- }
這里的方法攔截器主要是對(duì)你要攔截的類的方法進(jìn)行攔截,然后判斷該方法的類路徑+方法名稱+參數(shù)值組合的cache key在緩存cache中是否存在。如果存在就從緩存中取出該對(duì)象,轉(zhuǎn)換成我們要的返回類型。沒有的話就把該方法返回的對(duì)象添加到緩存中即可。值得主意的是當(dāng)前方法的參數(shù)和返回值的對(duì)象類型需要序列化。
我們需要在src目錄下添加applicationContext.xml完成對(duì)MethodCacheInterceptor攔截器的配置,該配置主意是注入我們的cache對(duì)象,哪個(gè)cache來(lái)管理對(duì)象緩存,然后哪些類、方法參與該攔截器的掃描。
添加配置如下:
- <context:component-scan base-package="com.hoo.common.interceptor"/>
- <!-- 配置eh緩存管理器 -->
- <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
- <!-- 配置一個(gè)簡(jiǎn)單的緩存工廠bean對(duì)象 -->
- <bean id="simpleCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
- <property name="cacheManager" ref="cacheManager" />
- <!-- 使用緩存 關(guān)聯(lián)ehcache.xml中的緩存配置 -->
- <property name="cacheName" value="mobileCache" />
- </bean>
- <!-- 配置一個(gè)緩存攔截器對(duì)象,處理具體的緩存業(yè)務(wù) -->
- <bean id="methodCacheInterceptor" class="com. hoo.common.interceptor.MethodCacheInterceptor">
- <property name="cache" ref="simpleCache"/>
- </bean>
- <!-- 參與緩存的切入點(diǎn)對(duì)象 (切入點(diǎn)對(duì)象,確定何時(shí)何地調(diào)用攔截器) -->
- <bean id="methodCachePointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
- <!-- 配置緩存aop切面 -->
- <property name="advice" ref="methodCacheInterceptor" />
- <!-- 配置哪些方法參與緩存策略 -->
- <!--
- .表示符合任何單一字元
- ### +表示符合前一個(gè)字元一次或多次
- ### *表示符合前一個(gè)字元零次或多次
- ### \Escape任何Regular expression使用到的符號(hào)
- -->
- <!-- .*表示前面的前綴(包括包名) 表示print方法-->
- <property name="patterns">
- <list>
- <value>com.hoo.rest.*RestService*\.*get.*</value>
- <value>com.hoo.rest.*RestService*\.*search.*</value>
- </list>
- </property>
- </bean>
- 在ehcache.xml中添加如下cache配置
- <cache name="mobileCache"
- maxElementsInMemory="10000"
- eternal="false"
- overflowToDisk="true"
- timeToIdleSeconds="1800"
- timeToLiveSeconds="3600"
- memoryStoreEvictionPolicy="LFU" />
原文鏈接:http://www.cnblogs.com/hoojo/archive/2012/07/12/2587556.html