Spring MVC3整合缓存Ehcache实现

最近项目中需要用到缓存,在网上搜了大堆资料,发现Ehcache很好用,用法也比较简单。用简单的配置就可以使用了,而且Ehcache可以对页面、对象、数据进行缓存。Spring对Ehcache的支持也非常好。EHCache支持内存和磁盘的缓存,支持多种淘汰算法(LRU、LFU和FIFO)。

一、本教程环境

Spring 3.2.5 支持 http://pan.baidu.com/s/1hq3To8k

Ehcache 对象、数据缓存: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

Ehcache Web页面缓存:http://ehcache.org/downloads/destination?name=ehcache-web-2.0.4-distribution.tar.gz&bucket=tcdistributions&file=ehcache-web-2.0.4-distribution.tar.gz

把jar加入到lib目录下

ehcache-core中可以找到 ehcache.xml 、ehcache.xsd着两个配置文件放到项目src下的配置文件目录

二、页面缓存

页面缓存主要用Filter过滤器对请求的url进行过滤,如果该url在缓存中出现。那么页面数据就从缓存对象中获取,并以gzip压缩后返回。其速度是没有压缩缓存时速度的3-5倍,效率相当之高!

在ehcache.xml中加入如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd">
<!--
name:Cache的唯一标识
maxElementsInMemory:内存中最大缓存对象数
maxElementsOnDisk:磁盘中最大缓存对象数,若是0表示无穷大
eternal:Element是否永久有效,一但设置了,timeout将不起作用
overflowToDisk:配置此属性,当内存中Element数量达到maxElementsInMemory时,Ehcache将会Element写到磁盘中
timeToIdleSeconds:设置Element在失效前的允许闲置时间。仅当element不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大
timeToLiveSeconds:设置Element在失效前允许存活时间。最大时间介于创建时间和失效时间之间。仅当element不是永久有效时使用,默认是0.,也就是element存活时间无穷大 意思是从cache中的某个元素从创建到消亡的时间,从创建开始计时,当超过这个时间,这个元素将被从cache中清除
diskPersistent:是否缓存虚拟机重启期数据
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)
-->
<pre name="code" class="html"> <diskStore path="java.io.tmpdir"/>
<defaultCache maxElementsInMemory="20000" overflowToDisk="true" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="3600" />
<cache name="SimplePageCachingFilter" maxElementsInMemory="20000" maxElementsOnDisk="20000" eternal="false" overflowToDisk="true" timeToIdleSeconds="3600" timeToLiveSeconds="3600" memoryStoreEvictionPolicy="LFU" /></ehcache>

拦截代码具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
[java] view plaincopyprint?
/**
* @Title: PageEhCacheFilter.java
* @Package com.cqut.tool.cache
* @Description: TODO(用一句话描述该文件做什么)
* @author matao@cqrainbowsoft.com
* @date 2014-8-1 上午12:29:00
* @version V1.0
*/
package com.cqut.tool.cache;
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;
/**
* 项目名称:<span style="font-family:Verdana, Geneva, Arial, Helvetica, sans-serif;">SpringMVCDemo</span> 类名称:PageEhCacheFilter 类描述: 创建人:熊海 创建时间:2014-8-1
* 上午12:29:00 修改人:熊海 修改时间:2014-8-1 上午12:29:00 修改备注:
*
* @version 1.0 <span style="font-family:Verdana, Geneva, Arial, Helvetica, sans-serif;">Superc102</span>
*/
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 就缓存该页面,否则执行正常的页面转向
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);
}
}
@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;
}
}

web.xml加入如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- ehcache页面缓存 -->
<filter>
<filter-name>PageEhCacheFilter</filter-name>
<filter-class>com.cqut.tool.cache.PageEhCacheFilter</filter-class>
<init-param>
<param-name>patterns</param-name>
<!-- 配置你需要缓存的url -->
<param-value>/module/jsp/news/newsCenter.jsp,indexPicController/getDimensionCode.do</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>PageEhCacheFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>PageEhCacheFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>

测试页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%>
<%@ page import="com.cqut.tool.util.PropertiesTool"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+PropertiesTool.getSystemPram("serverName")+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP 'testcache.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
<%=new Date() %>
</body>
</html>

当第一次请求这些页面后,这些页面就会被添加到缓存中,如果缓存没失效的话以后请求这些页面将会从缓存中获取。

把缓存测试页面加入web.xml的配置中后。如果时间是变动的,则表示该页面没有被缓存或是缓存已经过期,否则则是在缓存状态了。

三、对象缓存

对象缓存就是将查询的数据添加到缓存中,下次再次查询相同东西的时候直接从缓存中获取,而不去数据库中查询。

对象缓存一般是针对方法、类而来的。本教程结合Spring的Aop对象、方法缓存来说明。

在application.xml的命名空间中加入下面配置:

xmlns:cache=”http://www.springframework.org/schema/cache
xmlns:p=”http://www.springframework.org/schema/p
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd

在application.xml的中加入下面配置:

1
2
3
4
5
6
7
8
9
<!--start spring缓存 -->
<cache:annotation-driven /><!-- 支持缓存的配置项<span style="font-family:Verdana, Geneva, Arial, Helvetica, sans-serif;">注解</span> -->
<!--start 一般的spring_ehcache缓存管理 -->
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache"/>
<!-- EhCache library setup -->
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="classpath:ehcache.xml" p:shared="true"/>
<!-- 保持使用p:shared="true"同一个缓存 -->
<!--end 一般的spring_ehcache缓存管理 -->
<!--end spring缓存 -->

java测试方法:

1
2
3
4
5
6
@Override
@Cacheable(value="myCache",key="#columnCode+'ColumnsServicegetColumns'",condition="#columnCode<23")
public String getColumns(int columnCode){
System.out.println("请求后台:"+columnCode);
return "xhay";
}

@Cacheable注解可以用在方法或者类级别。当他应用于方法级别的时候,就是如上所说的缓存返回值了。当应用在类级别的时候,这个类的所有方法的返回值都将被缓存。

@Cacheable注解有三个参数,value是必须的,还有key和condition。第一个参数,也就是value指明ehcache.xml配置的缓存名称。

@Cacheable注解的方法的签名来作为key,当然你可以重写key,自定义key可以使用SpEL表达式。

@Cacheable的最后一个参数是condition(可选),同样的,也是引用一个SpEL表达式。但是这个参数将指明方法的返回结果是否被缓存。

在上面这个测试例子中,第一次访问的时候后台会打印日志“请求后台:”,下一次请求的时候直接从缓存中拿数据。不会有后台输出。

想对EhCache做进一步了解的可以参考这一片文章:http://raychase.iteye.com/blog/1545906