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.