內容緩存是Web應用中最普通的優化技術之一,例如,可以使用一個自定義地JSP標簽——我們將之命名為
這篇文章對上面描述的技術做了改進,通過使用JSP 2.0表達式語言(EL),允許JSP頁面為每一個請求和用戶定制緩存內容。緩存頁面片段可以包含未被JSP容器賦值的JSP表達式,在每一次頁面被執行時,由自定義標簽來確定這些表達式的值。因此,動態內容的建立被最優化,但是緩存片段可以含有部分由每一個請求使用本機JSP表達式語言產生的內容。通過JSP 2.0 EL API的幫助,Java開發者可以用表達式語言來使之成為可能。
內容緩存VS數據緩存
內容緩存不是唯一的選擇。例如, 從數據庫中提取的數據同樣可以被緩存。事實上,由於存儲的信息中不包含HTML markup,以及要求較少的內存,數據緩存可能更加高效率。然而在很多情況下,內存緩存更容易實現。假設在某個案例總,一個應用由大量事務對象,占用重要的CPU資源,產生復雜的數據,並且用JSP頁面來呈現這些數據。工作一切良好,直到某天突然地服務器的負載增加,需要一個緊急解決方案。這時在事務對象和呈現表達層之間建立一個緩存層,時一個非常不錯和有效的方案。但是必須非常快速和流暢地修改緩存動態內容的JSP頁面。相對於簡單的JSP頁面編輯,應用程序的業務邏輯變化通常要求更多的工作量和測試;另外,如果一個頁面從多個復合源聚合信息時,Web層僅有少量的改變。問題在於,當緩存信息變得失去時效時,緩存空間需要被釋放,而事務對象應該知道何時發生這種情況。然而,選擇實現內容緩存還是數據緩存,或者其他的優化技術,有很多不得不考慮的因素,有時是所開發的程序所特殊要求的。 數據緩存和內容緩存沒有必要互相排斥,它們可以一起使用。例如,在數據庫驅動的應用中;從數據庫中提取出來的數據,和呈現該數據的HTML分別被緩存起來。這與使用JSP實時生成的模板有些相似。這篇文章中討論的基於EL API技術說明如何使用JSP EL來將數據載入到呈現模板中。
使用JSP變量緩存動態內容
每當實現一個緩存機制是,都需要一個存儲緩存對象的方法,在這篇文章中涉及的是String類型的對象。 一種選擇是使用一個對象——緩存框架結構,或者使用Java maps來實現自定義的緩存方案。JSP已經擁有了稱為“scoped attributes”或“JSP variables”來提供ID——object映射,這正是緩存機制所需要的。對於使用page或者request scope,這是沒有意義的,而在應用范圍內,這是一個很好的存儲緩存內容的位置, 因為它被所有的用戶和頁面共享。當每一個用戶需要單獨緩存時,Session scope也可以被使用,但這不是很有效率。JSTL標簽庫可以被是與那個來緩存內容,通過使用JSP變量正如下例所示:
...
緩存頁面片段用下列語句輸出結果:
${applicationScope.cachedFragment}
當緩存片段需要被每一個請求所定制的時候,到底發生了什麼?
例如,如果希望包含一個計數器,需要緩存兩個片段:
...
...
可以使用下面語句輸出緩存內容:
${cachedFragment1} ${counter} ${cachedFragment2} 通過專門的標簽庫的幫助,需要定制的頁面片段的緩存變得異常容易了。上面已經提及,緩存內容可以被開始標簽(
...
...
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.el.ELException;
import javax.servlet.jsp.el.ExpressionEvaluator;
import java.io.IOException;public class JspUtils
{
public static Object eval( String expr, Class type, JspContext jspContext)
throws JspException
{
try
{
if (expr.indexOf("${") == -1) return expr;
ExpressionEvaluator evaluator= jspContext.getExpressionEvaluator();
return evaluator.evaluate(expr, type,
jspContext.getVariableResolver(), null);
} catch (ELException e)
{
throw new JspException(e);
}
}
...
}注意:JspUtils.eval()主要封裝了標准的ExpressionEvaluator。如果expr不包含${,JSP EL API不被調用,因為沒有JSP表達式。創建標簽庫描述符(TLD)文件JSP標簽庫需要一個標簽庫描述符(TLD)文件來自定義標簽的命名,它們的屬性,以及操作該標簽的Java類。jspcache.tld描述了兩個自定義標簽,
TLD文件包含在Web應用描述符文件(web.xml)中,這五個文件同樣包含一個初始參數指出cache是否可用。
理解
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-app_2_4.xsd"
version="2.4">
com.devsphere.articles.jspcache.enabled
import javax.servlet.ServletContext;
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;import java.io.StringWriter;
public class CacheTag extends SimpleTagSupport
{
public static final String CACHE_ENABLED =
"com.devsphere.articles.jspcache.enabled";
private String id; private int scope;
private boolean cacheEnabled; public CacheTag()
{
id = null;scope = PageContext.APPLICATION_SCOPE;
}public void setId(String id)
{
this.id = id;
}
public void setScope(String scope)
{
this.scope = JspUtils.checkScope(scope);
}
...
}
setScope()方法調用JspUtils.checkScope()來校驗已經
從String轉換為int類型的scope的屬性值。 ...public class JspUtils { ... public static int checkScope(String scope) { if ("page".equalsIgnoreCase(scope)) return PageContext.PAGE_SCOPE; else if ("request".equalsIgnoreCase(scope)) return PageContext.REQUEST_SCOPE; else if ("session".equalsIgnoreCase(scope)) return PageContext.SESSION_SCOPE; else if ("application".equalsIgnoreCase(scope)) return PageContext.APPLICATION_SCOPE; else throw new IllegalArgumentException ( "Invalid scope: " + scope); }}一旦CacheTag實例准備對標簽進行操作,JSP容器調用doTag()方法,用getJspContext()來獲得JSP context。這個對象被造型為PageContext,從而可以調用getServletContext()方法。servlet context用來獲取初始化參數的值,這個值標明緩存機制是否被啟用。如果緩存被啟用,doTag()嘗試使用id和scope屬性值來獲得緩存頁面片段。如果頁面片段還沒有被緩存,doTag()使用getJspBody().invoke()來執行由
public class CacheTag extends SimpleTagSupport
{
...
public void doTag() throws JspException, IOException
{
JspContext jspContext = getJspContext();
ServletContext application = ((PageContext)
jspContext).getServletContext();
String cacheEnabledParam= application.getInitParameter(CACHE_ENABLED);
cacheEnabled = cacheEnabledParam != null
&& cacheEnabledParam.equals("true");
if (cacheEnabled)
{
String cachedOutput= (String) jspContext.getAttribute(id, scope);
if (cachedOutput == null)
{
StringWriter buffer = new StringWriter();
getJspBody().invoke(buffer);
cachedOutput = buffer.toString();
jspContext.setAttribute(id, cachedOutput, scope);
}
String evaluatedOutput = (String)
JspUtils.eval( cachedOutput, String.class, jspContext);
jspContext.getOut().print(evaluatedOutput);
}
else getJspBody().invoke(null);
}
...
}注意一個單獨的JspUtils.eval()調用給所有的${…} 表達式賦值。因為一個包含了大量的${…}結構的text也是一個表達式。每一個緩存片段都可以被當作一個復雜的JSP表達式來進行處理。 IsCacheEnabled()方法返回cacheEnabled的值,這個值已經被doTag()初始化。IsCacheEnabled()方法返回cacheEnabled的值,這個值已經被doTag()初始化。
...public class CacheTag extends SimpleTagSupport
{
...
public boolean isCacheEnabled() { return cacheEnabled;
}
}
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
public class DynamicTag extends SimpleTagSupport
{
private String expr; public void setExpr(String expr)
{
this.expr = expr;
}
public void doTag() throws JspException, IOException
{
String output = "${" + expr + "}";
CacheTag ancestor = (CacheTag) findAncestorWithClass
( this, CacheTag.class);
if (ancestor == null || !ancestor.isCacheEnabled())
output = (String) JspUtils.eval
( output, String.class, getJspContext());
getJspContext().getOut().print(output);
}
}分析上面的代碼,大家可以注意到