oscache對于jsp/servlet的緩存是使用Filter來實現的,對應的類是com.opensymphony.oscache.web.filter.CacheFilter,既然是Filter那么要看的自然主要有三個方法:init、doFilter和destroy,這里#destroy()并沒有具體實現,只關注前兩個即可,首先看一下#init()方法,

public void init(FilterConfig filterConfig) {
config = filterConfig;

log.info("OSCache: Initializing CacheFilter with filter name " + config.getFilterName());

// 此變量用于防治請求被重復的緩存
requestFiltered = REQUEST_FILTERED + config.getFilterName();
log.info("Request filter attribute is " + requestFiltered);

// 讀取配置文件
Properties props = null;

try {
// 首先按照Filter參數指定的地方讀取配置文件,否則讀取默認位置的
String propertiesfile = config.getInitParameter("oscache-properties-file");


if (propertiesfile != null && propertiesfile.length() > 0) {
props = Config.loadProperties(propertiesfile,
"CacheFilter with filter name '" + config.getFilterName() + "'");
}

} catch (Exception e) {
log.info("OSCache: Init parameter 'oscache-properties-file' not set, using default.");
}

// 實例化ServletCacheAdministrator,ServletCacheAdministrator只有一個實例,這里需要關注一下
admin = ServletCacheAdministrator.getInstance(config.getServletContext(), props);

// 緩存超時時間
String timeParam = config.getInitParameter("time");

if (timeParam != null) {

try {
setTime(Integer.parseInt(timeParam));

} catch (NumberFormatException nfe) {
log.error("OSCache: Unexpected value for the init parameter 'time', defaulting to one hour. Message="
+ nfe.getMessage());
}
}

// 緩存范圍
String scopeParam = config.getInitParameter("scope");

if (scopeParam != null) {

if ("session".equalsIgnoreCase(scopeParam)) {
setCacheScope(PageContext.SESSION_SCOPE);

} else if ("application".equalsIgnoreCase(scopeParam)) {
setCacheScope(PageContext.APPLICATION_SCOPE);

} else {
log.error("OSCache: Wrong value '" + scopeParam
+ "' for init parameter 'scope', defaulting to 'application'.");
}

}

// 利用"計劃任務"表達式來處理超時時間
setCron(config.getInitParameter("cron"));

// 是否處理include
String fragmentParam = config.getInitParameter("fragment");

if (fragmentParam != null) {

if ("no".equalsIgnoreCase(fragmentParam)) {
setFragment(FRAGMENT_NO);

} else if ("yes".equalsIgnoreCase(fragmentParam)) {
setFragment(FRAGMENT_YES);

} else if ("auto".equalsIgnoreCase(fragmentParam)) {
setFragment(FRAGMENT_AUTODETECT);

} else {
log.error("OSCache: Wrong value '" + fragmentParam
+ "' for init parameter 'fragment', defaulting to 'auto detect'.");
}
}

// 是否處理URL里包括session id的請求
String nocacheParam = config.getInitParameter("nocache");

if (nocacheParam != null) {

if ("off".equalsIgnoreCase(nocacheParam)) {
nocache = NOCACHE_OFF;

} else if ("sessionIdInURL".equalsIgnoreCase(nocacheParam)) {
nocache = NOCACHE_SESSION_ID_IN_URL;

} else {
log.error("OSCache: Wrong value '" + nocacheParam
+ "' for init parameter 'nocache', defaulting to 'off'.");
}
}

// 是否處理寫入到response中的header屬性Last-Modified
String lastModifiedParam = config.getInitParameter("lastModified");

if (lastModifiedParam != null) {

if ("off".equalsIgnoreCase(lastModifiedParam)) {
lastModified = LAST_MODIFIED_OFF;

} else if ("on".equalsIgnoreCase(lastModifiedParam)) {
lastModified = LAST_MODIFIED_ON;

} else if ("initial".equalsIgnoreCase(lastModifiedParam)) {
lastModified = LAST_MODIFIED_INITIAL;

} else {
log.error("OSCache: Wrong value '" + lastModifiedParam
+ "' for init parameter 'lastModified', defaulting to 'initial'.");
}
}

// 是否處理寫入到response中的header屬性Expires
String expiresParam = config.getInitParameter("expires");

if (expiresParam != null) {

if ("off".equalsIgnoreCase(expiresParam)) {
setExpires(EXPIRES_OFF);

} else if ("on".equalsIgnoreCase(expiresParam)) {
setExpires(EXPIRES_ON);

} else if ("time".equalsIgnoreCase(expiresParam)) {
setExpires(EXPIRES_TIME);

} else {
log.error("OSCache: Wrong value '" + expiresParam
+ "' for init parameter 'expires', defaulting to 'on'.");
}
}

// 是否處理寫入到response中的header屬性Cache-Control
String cacheControlMaxAgeParam = config.getInitParameter("max-age");

if (cacheControlMaxAgeParam != null) {

if (cacheControlMaxAgeParam.equalsIgnoreCase("no init")) {
setCacheControlMaxAge(MAX_AGE_NO_INIT);

} else if (cacheControlMaxAgeParam.equalsIgnoreCase("time")) {
setCacheControlMaxAge(MAX_AGE_TIME);

} else {

try {
setCacheControlMaxAge(Long.parseLong(cacheControlMaxAgeParam));

} catch (NumberFormatException nfe) {
log.error("OSCache: Unexpected value for the init parameter 'max-age', defaulting to '60'. Message="
+ nfe.getMessage());
}
}
}

// ICacheKeyProvider的實例,用于創建緩存的key
ICacheKeyProvider cacheKeyProviderParam = (ICacheKeyProvider) instantiateFromInitParam(
"ICacheKeyProvider", ICacheKeyProvider.class, this.getClass().getName());

if (cacheKeyProviderParam != null) {
setCacheKeyProvider(cacheKeyProviderParam);
}

// ICacheGroupsProvider的實例,用于創建緩存的group名字
ICacheGroupsProvider cacheGroupsProviderParam = (ICacheGroupsProvider) instantiateFromInitParam(
"ICacheGroupsProvider", ICacheGroupsProvider.class, this.getClass().getName());

if (cacheGroupsProviderParam != null) {
setCacheGroupsProvider(cacheGroupsProviderParam);
}

// EntryRefreshPolicy的實例,用于指定緩存過期策略
EntryRefreshPolicy expiresRefreshPolicyParam = (EntryRefreshPolicy) instantiateFromInitParam(
"EntryRefreshPolicy", EntryRefreshPolicy.class, ExpiresRefreshPolicy.class.getName());

if (expiresRefreshPolicyParam != null) {
setExpiresRefreshPolicy(expiresRefreshPolicyParam);

} else {
setExpiresRefreshPolicy(new ExpiresRefreshPolicy(time));
}

// 指定哪些請求方式不去緩存,如GET,POST等
String disableCacheOnMethodsParam = config.getInitParameter("disableCacheOnMethods");

if (StringUtil.hasLength(disableCacheOnMethodsParam)) {
disableCacheOnMethods = StringUtil.split(disableCacheOnMethodsParam, ',');
}

}
這個方法主要是對Filter的init-param的載入還有緩存管理器類實例的創建(里面會包括一個Application Scope的Cache的創建還有oscache配置文件的讀取)。其中的這句調用時需要關注一下的,admin = ServletCacheAdministrator.getInstance(config.getServletContext(), props),也就是ServletCacheAdministrator類實例的創建。

public synchronized static ServletCacheAdministrator getInstance(ServletContext context, Properties p) {
// Cache在ServletContext中的屬性名
String adminKey = null;

if (p != null) {
// 這里是oscache配置文件中的cache.key屬性
adminKey = p.getProperty(CACHE_KEY_KEY);
}

if (adminKey == null) {
adminKey = DEFAULT_CACHE_KEY;
}
// ServletCacheAdministrator在ServletContext中的鍵值要加上"_admin"這個后綴
adminKey += CACHE_ADMINISTRATOR_KEY_SUFFIX;

// 先嘗試在ServletContext中找Cache,當然,第一次初始化時是不會找到的
ServletCacheAdministrator admin = (ServletCacheAdministrator) context.getAttribute(adminKey);


if (admin == null) {
// 實例化一個,并在ServletContext中設定好相關屬性
admin = new ServletCacheAdministrator(context, p);
Map admins = (Map) context.getAttribute(CACHE_ADMINISTRATORS_KEY);

if (admins == null) {
admins = new HashMap();
}
admins.put(adminKey, admin);
context.setAttribute(CACHE_ADMINISTRATORS_KEY, admins);
context.setAttribute(adminKey, admin);


if (log.isInfoEnabled()) {
log.info("Created new instance of ServletCacheAdministrator with key " + adminKey);
}

// 創建Application級別的Cache
admin.getAppScopeCache(context);
}


if (admin.context == null) {
admin.context = context;
}

return admin;
}


public Cache getAppScopeCache(ServletContext context) {
Cache cache;
// 首先嘗試在ServletContext中查詢App級的緩存
Object obj = context.getAttribute(getCacheKey());


if ((obj == null) || !(obj instanceof Cache)) {

if (log.isInfoEnabled()) {
log.info("Created new application-scoped cache at key: " + getCacheKey());
}

// 創建一個緩存實例并放入ServletContext中
cache = createCache(PageContext.APPLICATION_SCOPE, null);
context.setAttribute(getCacheKey(), cache);

} else {
cache = (Cache) obj;
}

return cache;
}


private ServletCache createCache(int scope, String sessionId) {
// 創建ServletCache
ServletCache newCache = new ServletCache(this, algorithmClass, cacheCapacity, scope);

// 這里的2個參數是用于給持久化緩存構建緩存目錄用的,這里要注意,Session級別的緩存可能會有問題
// 因為config是全局唯一的,在并發訪問的情況下如果多個session同時創建緩存會出現同步錯誤的
// 所以session級別緩存是不是應該慎用磁盤緩存呢?
config.set(HASH_KEY_SCOPE, "" + scope);
config.set(HASH_KEY_SESSION_ID, sessionId);

// 初始化Cache監聽器,包括磁盤緩存的處理類
newCache = (ServletCache) configureStandardListeners(newCache);


if (config.getProperty(CACHE_ENTRY_EVENT_LISTENERS_KEY) != null) {
// Add any event listeners that have been specified in the
// configuration
CacheEventListener[] listeners = getCacheEventListeners();


for (int i = 0; i < listeners.length; i++) {

if (listeners[i] instanceof ScopeEventListener) {
newCache.addCacheEventListener(listeners[i]);
}
}
}

return newCache;
}
看過#init()方法后就輪到#doFilter()方法了,這是真正每次對請求進行緩存的地方:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

throws ServletException, IOException {

if (log.isInfoEnabled()) {
log.info("OSCache: filter in scope " + cacheScope);
}

// 判斷是否請求已經緩存或者是否能緩存
// #isFilteredBefore()判斷request中是否包括了requestFiltered這個變量
// #isCacheableInternal()根據Filter的disableCacheOnMethods和nocache參數進行判斷

if (isFilteredBefore(request) || !isCacheableInternal(request)) {
chain.doFilter(request, response);
return;
}

// 設置當前的請求已經緩存了
request.setAttribute(requestFiltered, Boolean.TRUE);

HttpServletRequest httpRequest = (HttpServletRequest) request;

// checks if the response will be a fragment of a page
// 是否處理"include",如果是自動判斷那么要判斷請求中是否有javax.servlet.include.request_uri參數
boolean fragmentRequest = isFragment(httpRequest);

// 根據不同的緩存范圍來返回緩存實例
Cache cache;

if (cacheScope == PageContext.SESSION_SCOPE) {
// #getSessionScopeCache()中返回的Cache是保存在Session中的
cache = admin.getSessionScopeCache(httpRequest.getSession(true));

} else {
// #getAppScopeCache()中返回的Cache是保存在ServletContext中的
cache = admin.getAppScopeCache(config.getServletContext());
}

// 生成緩存的key,默認的cacheKeyProvider就是CacheFilter自己
// 成生的key默認是請求路徑+方法名(GET,POST)
String key = cacheKeyProvider.createCacheKey(httpRequest, admin, cache);


try {
// 查找緩存,如果還沒有加入緩存,會拋出NeedsRefreshException,進入異常處理路徑
ResponseContent respContent = (ResponseContent) cache.getFromCache(key, time, cron);


if (log.isInfoEnabled()) {
log.info("OSCache: Using cached entry for " + key);
}

boolean acceptsGZip = false;
// 這里是對客戶端緩存的處理,判斷下請求中是否包含If-Modified-Since頭信息

if ((!fragmentRequest) && (lastModified != LAST_MODIFIED_OFF)) {
long clientLastModified = httpRequest.getDateHeader(HEADER_IF_MODIFIED_SINCE);

// 如果請求中的最后修改時間大于緩存的最后修改時間,那么就返回狀態碼304

if ((clientLastModified != -1) && (clientLastModified >= respContent.getLastModified())) {
((HttpServletResponse) response).setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return;
}

// 判斷是否接受gzip壓縮的響應,通過判斷請求Header Accept-Encoding是否包含gzip
acceptsGZip = respContent.isContentGZiped() && acceptsGZipEncoding(httpRequest);
}

// 將緩存的內容寫入響應
respContent.writeTo(response, fragmentRequest, acceptsGZip);

} catch (NeedsRefreshException nre) {
// 如果緩存中還沒有想要的數據或者緩存需要刷新
boolean updateSucceeded = false;


try {

if (log.isInfoEnabled()) {
log.info("OSCache: New cache entry, cache stale or cache scope flushed for " + key);
}

// 這里用CacheHttpServletResponseWrapper來代替原來的response對象繼續請求處理
CacheHttpServletResponseWrapper cacheResponse = new CacheHttpServletResponseWrapper(
(HttpServletResponse) response, fragmentRequest, time * 1000L, lastModified, expires,
cacheControlMaxAge);
// 繼續調用后邊的Filter和Servlet
chain.doFilter(request, cacheResponse);
cacheResponse.flushBuffer();

// 這里判斷下響應碼是否是200,只有"OK"才會緩存

if (isCacheableInternal(cacheResponse)) {
// 創建緩存的group,默認的cacheGroupsProvider也是自己
String[] groups = cacheGroupsProvider.createCacheGroups(httpRequest, admin, cache);
// Store as the cache content the result of the response
// 將響應內容寫入緩存
cache.putInCache(key, cacheResponse.getContent(), groups, expiresRefreshPolicy, null);
updateSucceeded = true;

if (log.isInfoEnabled()) {
log.info("OSCache: New entry added to the cache with key " + key);
}
}

} finally {

if (!updateSucceeded) {
// 如果寫入緩存失敗,要取消更新,防止緩存出現錯誤的狀態
cache.cancelUpdate(key);
}
}
}
}
這個方法的整個流程通過代碼的注釋其實是很好理解的,要注意的地方有兩點:
首先,關注下異常路徑里更新緩存的地方,這里用CacheHttpServletResponseWrapper來代替原來的Response對象來繼續流程,通過對Response進行包裝(Decorator)的方式來記錄其要返回客戶端的Header和頁面內容(記錄到ResponseContent類的屬性中),其中頁面內容的捕獲是通過用SplitServletOutputStream類來代替原有的OutputStream來實現的,SplitServletOutputStream中包括了ResponseContent對象的一個ByteArrayOutputStream,每次寫入頁面響應的數據也都要記錄在ByteArrayOutputStream中一份,而在調用Cache的putInCache()方法時有一個cacheResponse.getContent()方法,會返回ResponseContent類的屬性,也就是真正要緩存的對象,并且將其ByteArrayOutputStream流中的數據"提交"到一個byte數組中保存下來,從而實現了響應數據的緩存。
其次,是將緩存的ResponseContent中的數據輸出的過程,也就是這一句:respContent.writeTo(response, fragmentRequest, acceptsGZip);基本可以理解為上邊緩存過程的逆過程,這里就不多說了,有興趣的可以了解下CacheHttpServletResponseWrapper,ResponseContent,SplitServletOutputStream的相關源代碼。
posted on 2010-12-20 08:42
臭美 閱讀(3416)
評論(2) 編輯 收藏 所屬分類:
Cache