对于缓存的设计实现,详细大家看过不少示例及项目源码,这边我要和大家分享下HtmlUnit实现的请求响应缓存Cache类,通过Collections.synchronizedMap将HashMap作为主实体存储结构,并通过静态内部类Entry,将cache的对象抽象化、高内聚化,但对外调用方法却比较简单通俗getCachedResponse(final WebRequest request),另外这边缓存内部类Entry通过对象化系统时间来判重,可借鉴参考对象equls比较,一般都是基于hashcode缓存或类的组合属性,代码如下
package com.gargoylesoftware.htmlunit;@b@@b@import java.io.Serializable;@b@import java.net.URL;@b@import java.util.Collections;@b@import java.util.Date;@b@import java.util.HashMap;@b@import java.util.Map;@b@import java.util.regex.Matcher;@b@import java.util.regex.Pattern;@b@@b@import org.apache.http.client.utils.DateUtils;@b@import org.w3c.dom.css.CSSStyleSheet;@b@@b@import com.gargoylesoftware.htmlunit.util.UrlUtils;@b@ @b@public class Cache implements Serializable {@b@@b@ /** The maximum size of the cache. */@b@ private int maxSize_ = 40;@b@@b@ private static final Pattern DATE_HEADER_PATTERN = Pattern.compile("-?\\d+");@b@@b@ @b@ private final Map<String, Entry> entries_ = Collections.synchronizedMap(new HashMap<String, Entry>(maxSize_));@b@@b@ @b@ private static class Entry implements Comparable<Entry>, Serializable {@b@ private final String key_;@b@ private WebResponse response_;@b@ private Object value_;@b@ private long lastAccess_;@b@@b@ Entry(final String key, final WebResponse response, final Object value) {@b@ key_ = key;@b@ response_ = response;@b@ value_ = value;@b@ lastAccess_ = System.currentTimeMillis();@b@ }@b@@b@ @b@ @Override@b@ public int compareTo(final Entry other) {@b@ if (lastAccess_ < other.lastAccess_) {@b@ return -1;@b@ }@b@ if (lastAccess_ == other.lastAccess_) {@b@ return 0;@b@ }@b@ return 1;@b@ }@b@ @b@ @Override@b@ public boolean equals(final Object obj) {@b@ return obj instanceof Entry && lastAccess_ == ((Entry) obj).lastAccess_;@b@ }@b@@b@ @b@ @Override@b@ public int hashCode() {@b@ return ((Long) lastAccess_).hashCode();@b@ }@b@ @b@ public void touch() {@b@ lastAccess_ = System.currentTimeMillis();@b@ }@b@ }@b@@b@ @b@ public boolean cacheIfPossible(final WebRequest request, final WebResponse response, final Object toCache) {@b@ if (isCacheable(request, response)) {@b@ final URL url = response.getWebRequest().getUrl();@b@ if (url == null) {@b@ return false;@b@ }@b@@b@ final Entry entry = new Entry(UrlUtils.normalize(url), response, toCache);@b@ entries_.put(entry.key_, entry);@b@ deleteOverflow();@b@ return true;@b@ }@b@@b@ return false;@b@ }@b@@b@ @b@ public void cache(final String css, final CSSStyleSheet styleSheet) {@b@ final Entry entry = new Entry(css, null, styleSheet);@b@ entries_.put(entry.key_, entry);@b@ deleteOverflow();@b@ }@b@@b@ @b@ protected void deleteOverflow() {@b@ synchronized (entries_) {@b@ while (entries_.size() > maxSize_) {@b@ final Entry oldestEntry = Collections.min(entries_.values());@b@ entries_.remove(oldestEntry.key_);@b@ if (oldestEntry.response_ != null) {@b@ oldestEntry.response_.cleanUp();@b@ }@b@ }@b@ }@b@ }@b@@b@ @b@ protected boolean isCacheable(final WebRequest request, final WebResponse response) {@b@ return HttpMethod.GET == response.getWebRequest().getHttpMethod()@b@ && isCacheableContent(response);@b@ }@b@@b@ @b@ protected boolean isCacheableContent(final WebResponse response) {@b@ final Date lastModified = parseDateHeader(response, "Last-Modified");@b@ final Date expires = parseDateHeader(response, "Expires");@b@@b@ final long delay = 10 * org.apache.commons.lang3.time.DateUtils.MILLIS_PER_MINUTE;@b@ final long now = getCurrentTimestamp();@b@@b@ return expires != null && (expires.getTime() - now > delay)@b@ || (expires == null && lastModified != null && (now - lastModified.getTime() > delay));@b@ }@b@@b@ @b@ protected long getCurrentTimestamp() {@b@ return System.currentTimeMillis();@b@ }@b@@b@ @b@ protected Date parseDateHeader(final WebResponse response, final String headerName) {@b@ final String value = response.getResponseHeaderValue(headerName);@b@ if (value == null) {@b@ return null;@b@ }@b@ final Matcher matcher = DATE_HEADER_PATTERN.matcher(value);@b@ if (matcher.matches()) {@b@ return new Date();@b@ }@b@ return DateUtils.parseDate(value);@b@ }@b@@b@ @b@ public WebResponse getCachedResponse(final WebRequest request) {@b@ final Entry cachedEntry = getCacheEntry(request);@b@ if (cachedEntry == null) {@b@ return null;@b@ }@b@ return cachedEntry.response_;@b@ }@b@@b@ @b@ public Object getCachedObject(final WebRequest request) {@b@ final Entry cachedEntry = getCacheEntry(request);@b@ if (cachedEntry == null) {@b@ return null;@b@ }@b@ return cachedEntry.value_;@b@ }@b@@b@ private Entry getCacheEntry(final WebRequest request) {@b@ if (HttpMethod.GET != request.getHttpMethod()) {@b@ return null;@b@ }@b@@b@ final URL url = request.getUrl();@b@ if (url == null) {@b@ return null;@b@ }@b@ final Entry cachedEntry = entries_.get(UrlUtils.normalize(url));@b@ if (cachedEntry == null) {@b@ return null;@b@ }@b@ synchronized (entries_) {@b@ cachedEntry.touch();@b@ }@b@ return cachedEntry;@b@ }@b@@b@ @b@ public CSSStyleSheet getCachedStyleSheet(final String css) {@b@ final Entry cachedEntry = entries_.get(css);@b@ if (cachedEntry == null) {@b@ return null;@b@ }@b@ synchronized (entries_) {@b@ cachedEntry.touch();@b@ }@b@ return (CSSStyleSheet) cachedEntry.value_;@b@ }@b@@b@ @b@ public int getMaxSize() {@b@ return maxSize_;@b@ }@b@@b@ /**@b@ * Sets the cache's maximum size. This is the maximum number of files that will@b@ * be cached. The default is <tt>25</tt>.@b@ *@b@ * @param maxSize the cache's maximum size (must be >= 0)@b@ */@b@ public void setMaxSize(final int maxSize) {@b@ if (maxSize < 0) {@b@ throw new IllegalArgumentException("Illegal value for maxSize: " + maxSize);@b@ }@b@ maxSize_ = maxSize;@b@ deleteOverflow();@b@ }@b@@b@ /**@b@ * Returns the number of entries in the cache.@b@ *@b@ * @return the number of entries in the cache@b@ */@b@ public int getSize() {@b@ return entries_.size();@b@ }@b@@b@ /**@b@ * Clears the cache.@b@ */@b@ public void clear() {@b@ synchronized (entries_) {@b@ for (final Entry entry : entries_.values()) {@b@ if (entry.response_ != null) {@b@ entry.response_.cleanUp();@b@ }@b@ }@b@ entries_.clear();@b@ }@b@ }@b@@b@}