Ehcache学习笔记(四) Web Caching 页面级别缓存
http://ehcache.org/documentation/modules/web-caching#caching-headers官方示例
页面缓存SimplePageCachingFilter
页面缓存主要用Filter过滤器对请求的url进行过滤,如果该url在缓存中出现。那么页面数据就从缓存对象中获取,并以gzip压缩后返回。其速度是没有压缩缓存时速度的3-5倍,效率相当之高!其中页面缓存的过滤器有CachingFilter,一般要扩展filter或是自定义Filter都继承该CachingFilter。
CachingFilter功能可以对HTTP响应的内容进行缓存。这种方式缓存数据的粒度比较粗,例如缓存整张页面。它的优点是使用简单、效率高,缺点是不够灵活,可重用程度不高。
EHCache使用SimplePageCachingFilter类实现Filter缓存。该类继承自CachingFilter,有默认产生cache key的calculateKey()方法,该方法使用HTTP请求的URI和查询条件来组成key。也可以自己实现一个Filter,同样继承CachingFilter类,然后覆写calculateKey()方法,生成自定义的key。
CachingFilter输出的数据会根据浏览器发送的Accept-Encoding头信息进行Gzip压缩。
在使用Gzip压缩时,需注意两个问题:
1. Filter在进行Gzip压缩时,采用系统默认编码,对于使用GBK编码的中文网页来说,需要将操作系统的语言设置为:zh_CN.GBK,否则会出现乱码的问题。
2. 默认情况下CachingFilter会根据浏览器发送的请求头部所包含的Accept-Encoding参数值来判断是否进行Gzip压缩。虽然IE6/7浏览器是支持Gzip压缩的,但是在发送请求的时候却不带该参数。为了对IE6/7也能进行Gzip压缩,可以通过继承CachingFilter,实现自己的Filter,然后在具体的实现中覆写方法acceptsGzipEncoding。
具体实现参考:
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;
}
具体代码
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
<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>
配置Servler时有3个参数可以配置
Init-Params
The following init-params are supported:
cacheName
- the name in ehcache.xml used by the filter.blockingTimeoutMillis
- the time, in milliseconds, to wait for the filter chain to return with a response on a cache miss. This is useful to fail fast in the event of an infrastructure failure.-
varyHeader
- set to true to set Vary:Accept-Encoding in the response when doing Gzip. This header is needed to support HTTP proxies however it is off by default.<init-param>
<param-name>varyHeader</param-name>
<param-value>true</param-value>
</init-param> cacheName使用的cache.xml中的名字 具体使用哪个cache
blockingTimeoutMillis 这个属性是 当多个请求访问被缓存页面的时候 默认的执行情况是这样的,当第一个请求去上缓存里查找页面时 如果缓存为空 那么该请求 首先 -》加载页面 -》然后存入缓存 -》然后返回。假如同时有多个请求访问该资源 那么只有第一个请求能够继续往下执行 其他的请求会阻塞住 直到第一个请求将资源放入缓存。这个属性就是配置缓存的失效时间
varyHeader 配置是否对缓存进行GZIP压缩
protected PageInfo buildPageInfo(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws Exception { // Look up the cached page final String key = calculateKey(request); PageInfo pageInfo = null; try { checkNoReentry(request); Element element = blockingCache.get(key); //这里只有有一个请求能够进入 其他请求如果拿到结果为NULL则阻塞在这里 //知道第一个请求将资源放入缓存 if (element == null || element.getObjectValue() == null) { try { // Page is not cached - build the response, cache it, and // send to client pageInfo = buildPage(request, response, chain); if (pageInfo.isOk()) { if (LOG.isDebugEnabled()) { LOG.debug("PageInfo ok. Adding to cache " + blockingCache.getName() + " with key " + key); } blockingCache.put(new Element(key, pageInfo)); } else { if (LOG.isDebugEnabled()) { LOG.debug("PageInfo was not ok(200). Putting null into cache " + blockingCache.getName() + " with key " + key); } blockingCache.put(new Element(key, null)); } } catch (final Throwable throwable) { // Must unlock the cache if the above fails. Will be logged // at Filter blockingCache.put(new Element(key, null)); throw new Exception(throwable); } } else { pageInfo = (PageInfo) element.getObjectValue(); } } catch (LockTimeoutException e) { // do not release the lock, because you never acquired it throw e; } finally { // all done building page, reset the re-entrant flag visitLog.clear(); } return pageInfo; }
SimplePageFragmentCachingFilter
该缓存对部分页面缓存
The SimplePageFragmentCachingFilter does everything that SimplePageCachingFilter does, except it never gzips, so the fragments can be combined. There is a variant of this filter which sets browser caching headers, because that is only applicable to the entire page.
这是官方的解释 大概意思就是和上一个缓存没什么区别 唯一的就是不适用GZIP 所以可以对部分页面缓存 如 页面的 header footer等
可以使用 <jsp:include page=""></jsp:include>
具体配置方法同上
SimpleCachingHeadersPageCachingFilter
The SimpleCachingHeadersPageCachingFilter
extends SimplePageCachingFilter
to provide the HTTP cache headers: ETag, Last-Modified and Expires. It supports conditional GET. Because browsers and other HTTP clients have the expiry information returned in the response headers, they do not even need to request the page again. Even once the local browser copy has expired, the browser will do a conditional GET. So why would you ever want to use SimplePageCachingFilter, which does not set these headers?
The answer is that in some caching scenarios you may wish to remove a page before its natural expiry. Consider a scenario where a web page shows dynamic data. Under Ehcache the Element can be removed at any time. However if a browser is holding expiry information, those browsers will have to wait until the expiry time before getting updated. The caching in this scenario is more about defraying server load rather than minimising browser calls.
这个缓存用的相对较少 是通过HTTP cache headers 让浏览器端缓存 那么每次刷新不在发送请求。
缺点就是如果服务器端对数据进行了调整 客服端无法马上更新数据
具体配置方法同上
Example web.xml configuration <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee version="2.5"> <filter> <filter-name>CachePage1CachingFilter</filter-name> <filter-class>net.sf.ehcache.constructs.web.filter.SimplePageCachingFilter </filter-class> <init-param> <param-name>suppressStackTrace</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>cacheName</param-name> <param-value>CachePage1CachingFilter</param-value> </init-param> </filter> <filter> <filter-name>SimplePageFragmentCachingFilter</filter-name> <filter-class>net.sf.ehcache.constructs.web.filter.SimplePageFragmentCachingFilter </filter-class> <init-param> <param-name>suppressStackTrace</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>cacheName</param-name> <param-value>SimplePageFragmentCachingFilter</param-value> </init-param> </filter> <filter> <filter-name>SimpleCachingHeadersPageCachingFilter</filter-name> <filter-class>net.sf.ehcache.constructs.web.filter.SimpleCachingHeadersPageCachingFilter </filter-class> <init-param> <param-name>suppressStackTrace</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>cacheName</param-name> <param-value>CachedPage2Cache</param-value> </init-param> </filter> <!-- This is a filter chain. They are executed in the order below. Do not change the order. --> <filter-mapping> <filter-name>CachePage1CachingFilter</filter-name> <url-pattern>/CachedPage.jsp</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>INCLUDE</dispatcher> <dispatcher>FORWARD</dispatcher> </filter-mapping> <filter-mapping> <filter-name>SimplePageFragmentCachingFilter</filter-name> <url-pattern>/include/Footer.jsp</url-pattern> </filter-mapping> <filter-mapping> <filter-name>SimplePageFragmentCachingFilter</filter-name> <url-pattern>/fragment/CachedFragment.jsp</url-pattern> </filter-mapping> <filter-mapping> <filter-name>SimpleCachingHeadersPageCachingFilter</filter-name> <url-pattern>/CachedPage2.jsp</url-pattern> </filter-mapping> An ehcache.xml configuration file, matching the above would then be: <Ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../main/config/ehcache.xsd"> <diskStore path="auto/default/path"/> <defaultCache maxEntriesLocalHeap="10" eternal="false" timeToIdleSeconds="5" timeToLiveSeconds="10"> <persistence strategy="localTempSwap"/> /> <!-- Page and Page Fragment Caches --> <cache name="CachePage1CachingFilter" maxEntriesLocalHeap="10" eternal="false" timeToIdleSeconds="10000" timeToLiveSeconds="10000"> <persistence strategy="localTempSwap"/> </cache> <cache name="CachedPage2Cache" maxEntriesLocalHeap="10" eternal="false" timeToLiveSeconds="3600"> <persistence strategy="localTempSwap"/> </cache> <cache name="SimplePageFragmentCachingFilter" maxEntriesLocalHeap="10" eternal="false" timeToIdleSeconds="10000" timeToLiveSeconds="10000"> <persistence strategy="localTempSwap"/> </cache> <cache name="SimpleCachingHeadersTimeoutPageCachingFilter" maxEntriesLocalHeap="10" eternal="false" timeToIdleSeconds="10000" timeToLiveSeconds="10000"> <persistence strategy="localTempSwap"/> </cache> </ehcache>
有几个异常需要注意 现在贴出来
CachingFilter Exceptions
Additional exception types have been added to the Caching Filter.
FilterNonReentrantException
Thrown when it is detected that a caching filter's doFilter method is reentered by the same thread. Reentrant calls will block indefinitely because the first request has not yet unblocked the cache.
解释: 就是一个请求循环调用了该资源
ResponseHeadersNotModifiableException
Same as FilterNonReentrantException.
AlreadyGzippedException
This exception is thrown when a gzip is attempted on already gzipped content.
The web package performs gzipping operations. One cause of problems on web browsers is getting content that is double or triple gzipped. They will either get unreadable content or a blank page.
当程序试图 gzip一个已经gzip了的内容是抛出该异常
ResponseHeadersNotModifiableException
A gzip encoding header needs to be added for gzipped content. The HttpServletResponse#setHeader()
method is used for that purpose. If the header had already been set, the new value normally overwrites the previous one. In some cases according to the servlet specification, setHeader silently fails. Two scenarios where this happens are:
- The response is committed.
RequestDispatcher#include
method caused the request.