內容緩存是Web應用中最普通的優(yōu)化技術之一,例如,可以使用一個自定義地JSP標簽——我們將之命名為<jc:cache>——由<jc:cache>和</jc:cache>將每一個需要被緩存的頁面片段封裝起來。任何自定義標簽可以控制它所包含部分 (也即預先封裝的頁面片段)在何時執(zhí)行,并且動態(tài)輸出結果可以被捕獲。<jc:cache>標簽使得JSP容器(例如Tomcat)只生成內容一次,作為應用程序范圍內的JSP變量,來存儲每一個緩存片段。每次JSP頁面被執(zhí)行時,自定義標簽將緩存頁面片段載入而無需再次執(zhí)行JSP代碼來生成輸出結果。作為Jakarta工程的一個部分,標簽庫的開發(fā)使用了這項技術。當被緩存內容無需被每一個用戶或者請求所定制的時候,它工作的十分良好。
這篇文章對上面描述的技術做了改進,通過使用JSP 2.0表達式語言(EL),允許JSP頁面為每一個請求和用戶定制緩存內容。緩存頁面片段可以包含未被JSP容器賦值的JSP表達式,在每一次頁面被執(zhí)行時,由自定義標簽來確定這些表達式的值。因此,動態(tài)內容的建立被化,但是緩存片段可以含有部分由每一個請求使用本機JSP表達式語言產生的內容。通過JSP 2.0 EL API的幫助,Java開發(fā)者可以用表達式語言來使之成為可能。
內容緩存VS數(shù)據(jù)緩存
內容緩存不是的選擇。例如, 從數(shù)據(jù)庫中提取的數(shù)據(jù)同樣可以被緩存。事實上,由于存儲的信息中不包含HTML markup,以及要求較少的內存,數(shù)據(jù)緩存可能更加高效率。然而在很多情況下,內存緩存更容易實現(xiàn)。假設在某個案例總,一個應用由大量事務對象,占用重要的CPU資源,產生復雜的數(shù)據(jù),并且用JSP頁面來呈現(xiàn)這些數(shù)據(jù)。工作一切良好,直到某天突然地服務器的負載增加,需要一個緊急解決方案。這時在事務對象和呈現(xiàn)表達層之間建立一個緩存層,時一個非常不錯和有效的方案。但是必須非常快速和流暢地修改緩存動態(tài)內容的JSP頁面。相對于簡單的JSP頁面編輯,應用程序的業(yè)務邏輯變化通常要求更多的工作量和測試;另外,如果一個頁面從多個復合源聚合信息時,Web層僅有少量的改變。問題在于,當緩存信息變得失去時效時,緩存空間需要被釋放,而事務對象應該知道何時發(fā)生這種情況。然而,選擇實現(xiàn)內容緩存還是數(shù)據(jù)緩存,或者其他的優(yōu)化技術,有很多不得不考慮的因素,有時是所開發(fā)的程序所特殊要求的?! ?shù)據(jù)緩存和內容緩存沒有必要互相排斥,它們可以一起使用。例如,在數(shù)據(jù)庫驅動的應用中;從數(shù)據(jù)庫中提取出來的數(shù)據(jù),和呈現(xiàn)該數(shù)據(jù)的HTML分別被緩存起來。這與使用JSP實時生成的模板有些相似。這篇文章中討論的基于EL API技術說明如何使用JSP EL來將數(shù)據(jù)載入到呈現(xiàn)模板中。
使用JSP變量緩存動態(tài)內容
每當實現(xiàn)一個緩存機制是,都需要一個存儲緩存對象的方法,在這篇文章中涉及的是String類型的對象。 一種選擇是使用一個對象——緩存框架結構,或者使用Java maps來實現(xiàn)自定義的緩存方案。JSP已經(jīng)擁有了稱為“scoped attributes”或“JSP variables”來提供ID——object映射,這正是緩存機制所需要的。對于使用page或者request scope,這是沒有意義的,而在應用范圍內,這是一個很好的存儲緩存內容的位置, 因為它被所有的用戶和頁面共享。當每一個用戶需要單獨緩存時,Session scope也可以被使用,但這不是很有效率。JSTL標簽庫可以被是與那個來緩存內容,通過使用JSP變量正如下例所示:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:if test="${empty cachedFragment}">
<c:set var="cachedFragment" scope="application">
...
</c:set></c:if>
緩存頁面片段用下列語句輸出結果:
${applicationScope.cachedFragment}
當緩存片段需要被每一個請求所定制的時候,到底發(fā)生了什么?
例如,如果希望包含一個計數(shù)器,需要緩存兩個片段:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:if test="${sessionScope.counter == null}">
<c:set var="counter" scope="session" value="0"/>
</c:if><c:set var="counter" value="${counter+1}" scope="session"/>
<c:if test="${empty cachedFragment1}">
<c:set var="cachedFragment1" scope="application">
...
</c:set></c:if><c:if test="${empty cachedFragment2}">
<c:set var="cachedFragment2" scope="application">
...
</c:set></c:if>
可以使用下面語句輸出緩存內容:
${cachedFragment1} ${counter} ${cachedFragment2}
通過專門的標簽庫的幫助,需要定制的頁面片段的緩存變得異常容易了。上面已經(jīng)提及,緩存內容可以被開始標簽(<jc:cache>)和結尾標簽(</jc:cache>)封裝起來。而每一個定制可以使用另一個標簽(<jc:dynamic expr="..."/>)輸出一個JSP表達式(${...})來表現(xiàn)。動態(tài)內容用JSP表達式緩存并在每一次緩存內容被輸出時賦值。在下面的部分可以看到這是如何實現(xiàn)的。Counter.jsp緩存了一個包含計數(shù)器的頁面片段,當每一次用戶刷新這個頁面的時候計數(shù)器會自動+1。
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="jc" uri="http://devsphere.com/articles/jspcache" %>
<c:if test="${sessionScope.counter == null}">
<c:set var="counter" scope="session" value="0"/>
</c:if><c:set var="counter" value="${counter+1}" scope="session"/>
<jc:cache id="cachedFragmentWithCounter">
...
<jc:dynamic expr="sessionScope.counter"/>
...
</jc:cache>
JSP 變量易于使用,對于簡單的Web apps,這是一個不錯的內容緩存方案。然而,如果應用程序產生大量的動態(tài)內容,沒有對緩存大小的控制無疑是一個問題。一種專用的緩存框架結構能夠提供一個更加有力的方案,允許對緩存的監(jiān)視,限制緩存大小,控制緩存策略,等等……
使用JSP 2.0表達式語言API
JSP容器(例如Tomcat)對應用EL API的JSP頁面中的表達式予以賦值,并且可以被Java代碼所使用。這允許在Web頁面外應用JSP EL作開發(fā),例如,對XML文件、基于文本的資源以及自定義腳本。當需要控制何時對Web頁面中的表達式進行賦值或者書寫與之相關的表達式時,EL API同樣是有用的。例如,緩存頁面片段可以包含自定義JSP表達式,并且當每一次緩存內容被輸出時,EL API將用來給這些表達式賦值或者重新賦值。
文章提供了一個例子程序(參見文末資源部分),這個應用程序包含了一個Java類(JspUtils)和類中包含一個方法eval(),這個方法有三個參數(shù):JSP表達式、表達式的期望類型和一個JSP context對象。Eval()方法從JSP context中取得ExpressionEvaluator并且調用evaluate()方法,通過表達式、表達式的期望類型、和一個從JSP congtext中獲得的變量。JspUtils.eval()方法返回表達式的值。
package com.devsphere.articles.jspcache;
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表達式。
這篇文章對上面描述的技術做了改進,通過使用JSP 2.0表達式語言(EL),允許JSP頁面為每一個請求和用戶定制緩存內容。緩存頁面片段可以包含未被JSP容器賦值的JSP表達式,在每一次頁面被執(zhí)行時,由自定義標簽來確定這些表達式的值。因此,動態(tài)內容的建立被化,但是緩存片段可以含有部分由每一個請求使用本機JSP表達式語言產生的內容。通過JSP 2.0 EL API的幫助,Java開發(fā)者可以用表達式語言來使之成為可能。
內容緩存VS數(shù)據(jù)緩存
內容緩存不是的選擇。例如, 從數(shù)據(jù)庫中提取的數(shù)據(jù)同樣可以被緩存。事實上,由于存儲的信息中不包含HTML markup,以及要求較少的內存,數(shù)據(jù)緩存可能更加高效率。然而在很多情況下,內存緩存更容易實現(xiàn)。假設在某個案例總,一個應用由大量事務對象,占用重要的CPU資源,產生復雜的數(shù)據(jù),并且用JSP頁面來呈現(xiàn)這些數(shù)據(jù)。工作一切良好,直到某天突然地服務器的負載增加,需要一個緊急解決方案。這時在事務對象和呈現(xiàn)表達層之間建立一個緩存層,時一個非常不錯和有效的方案。但是必須非常快速和流暢地修改緩存動態(tài)內容的JSP頁面。相對于簡單的JSP頁面編輯,應用程序的業(yè)務邏輯變化通常要求更多的工作量和測試;另外,如果一個頁面從多個復合源聚合信息時,Web層僅有少量的改變。問題在于,當緩存信息變得失去時效時,緩存空間需要被釋放,而事務對象應該知道何時發(fā)生這種情況。然而,選擇實現(xiàn)內容緩存還是數(shù)據(jù)緩存,或者其他的優(yōu)化技術,有很多不得不考慮的因素,有時是所開發(fā)的程序所特殊要求的?! ?shù)據(jù)緩存和內容緩存沒有必要互相排斥,它們可以一起使用。例如,在數(shù)據(jù)庫驅動的應用中;從數(shù)據(jù)庫中提取出來的數(shù)據(jù),和呈現(xiàn)該數(shù)據(jù)的HTML分別被緩存起來。這與使用JSP實時生成的模板有些相似。這篇文章中討論的基于EL API技術說明如何使用JSP EL來將數(shù)據(jù)載入到呈現(xiàn)模板中。
使用JSP變量緩存動態(tài)內容
每當實現(xiàn)一個緩存機制是,都需要一個存儲緩存對象的方法,在這篇文章中涉及的是String類型的對象。 一種選擇是使用一個對象——緩存框架結構,或者使用Java maps來實現(xiàn)自定義的緩存方案。JSP已經(jīng)擁有了稱為“scoped attributes”或“JSP variables”來提供ID——object映射,這正是緩存機制所需要的。對于使用page或者request scope,這是沒有意義的,而在應用范圍內,這是一個很好的存儲緩存內容的位置, 因為它被所有的用戶和頁面共享。當每一個用戶需要單獨緩存時,Session scope也可以被使用,但這不是很有效率。JSTL標簽庫可以被是與那個來緩存內容,通過使用JSP變量正如下例所示:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:if test="${empty cachedFragment}">
<c:set var="cachedFragment" scope="application">
...
</c:set></c:if>
緩存頁面片段用下列語句輸出結果:
${applicationScope.cachedFragment}
當緩存片段需要被每一個請求所定制的時候,到底發(fā)生了什么?
例如,如果希望包含一個計數(shù)器,需要緩存兩個片段:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:if test="${sessionScope.counter == null}">
<c:set var="counter" scope="session" value="0"/>
</c:if><c:set var="counter" value="${counter+1}" scope="session"/>
<c:if test="${empty cachedFragment1}">
<c:set var="cachedFragment1" scope="application">
...
</c:set></c:if><c:if test="${empty cachedFragment2}">
<c:set var="cachedFragment2" scope="application">
...
</c:set></c:if>
可以使用下面語句輸出緩存內容:
${cachedFragment1} ${counter} ${cachedFragment2}
通過專門的標簽庫的幫助,需要定制的頁面片段的緩存變得異常容易了。上面已經(jīng)提及,緩存內容可以被開始標簽(<jc:cache>)和結尾標簽(</jc:cache>)封裝起來。而每一個定制可以使用另一個標簽(<jc:dynamic expr="..."/>)輸出一個JSP表達式(${...})來表現(xiàn)。動態(tài)內容用JSP表達式緩存并在每一次緩存內容被輸出時賦值。在下面的部分可以看到這是如何實現(xiàn)的。Counter.jsp緩存了一個包含計數(shù)器的頁面片段,當每一次用戶刷新這個頁面的時候計數(shù)器會自動+1。
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="jc" uri="http://devsphere.com/articles/jspcache" %>
<c:if test="${sessionScope.counter == null}">
<c:set var="counter" scope="session" value="0"/>
</c:if><c:set var="counter" value="${counter+1}" scope="session"/>
<jc:cache id="cachedFragmentWithCounter">
...
<jc:dynamic expr="sessionScope.counter"/>
...
</jc:cache>
JSP 變量易于使用,對于簡單的Web apps,這是一個不錯的內容緩存方案。然而,如果應用程序產生大量的動態(tài)內容,沒有對緩存大小的控制無疑是一個問題。一種專用的緩存框架結構能夠提供一個更加有力的方案,允許對緩存的監(jiān)視,限制緩存大小,控制緩存策略,等等……
使用JSP 2.0表達式語言API
JSP容器(例如Tomcat)對應用EL API的JSP頁面中的表達式予以賦值,并且可以被Java代碼所使用。這允許在Web頁面外應用JSP EL作開發(fā),例如,對XML文件、基于文本的資源以及自定義腳本。當需要控制何時對Web頁面中的表達式進行賦值或者書寫與之相關的表達式時,EL API同樣是有用的。例如,緩存頁面片段可以包含自定義JSP表達式,并且當每一次緩存內容被輸出時,EL API將用來給這些表達式賦值或者重新賦值。
文章提供了一個例子程序(參見文末資源部分),這個應用程序包含了一個Java類(JspUtils)和類中包含一個方法eval(),這個方法有三個參數(shù):JSP表達式、表達式的期望類型和一個JSP context對象。Eval()方法從JSP context中取得ExpressionEvaluator并且調用evaluate()方法,通過表達式、表達式的期望類型、和一個從JSP congtext中獲得的變量。JspUtils.eval()方法返回表達式的值。
package com.devsphere.articles.jspcache;
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表達式。

