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 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 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 需要加入:


	
		PageEhCacheFilter
		com.voole.lzw.common.cache.PageEhCacheFilter
		
			patterns
			
			/jsp/*
		
	
	
		PageEhCacheFilter
		*.action
	
	
		PageEhCacheFilter
		*.jsp
	

cacheContext.xml:



	
    
	
		
	
    
    
	
		
		
	
    
    
	
		
	
	
	
		
	
    
    
	
	    
	     
	     
	      
	        
	            com.voole.lzw.service.*.Service.*.find.*
	        
	      
    
    
      
	      
	      
	        
	            com.voole.lzw.service.*.Service.*.update.*
	        
	      
    
    

ehcache.xml:



 
	
	
	
	
		
	
		
	
  

applicationContext.xml:



    
    
	
	
	
	
	
	
		


感谢并转载:

http://78425665.iteye.com/blog/1564719

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

你可能感兴趣的:(Spring)