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://78425665.iteye.com/blog/1564719

http://www.cnblogs.com/hoojo/archive/2012/07/12/2587556.html

你可能感兴趣的:(Ehcache 整合Spring 使用对象缓存、页面缓存)