首页

解读HtmlUnit缓存Cache模型设计源码

标签:gargoylesoftware,htmlunit,源码解读,设计,存储结构,equals判重     发布时间:2016-09-08   

对于缓存的设计实现,详细大家看过不少示例及项目源码,这边我要和大家分享下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 &gt;= 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@}