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