內容緩存是Web應用中最普通的優化技術之一例如可以使用一個自定義地JSP標簽——我們將之命名為——由和將每一個需要被緩存的頁面片段封裝起來任何自定義標簽可以控制它所包含部分 (也即預先封裝的頁面片段)在何時執行並且動態輸出結果可以被捕獲標簽使得JSP容器(例如Tomcat)只生成內容一次作為應用程序范圍內的JSP變量來存儲每一個緩存片段每次JSP頁面被執行時自定義標簽將緩存頁面片段載入而無需再次執行JSP代碼來生成輸出結果作為Jakarta工程的一個部分標簽庫的開發使用了這項技術當被緩存內容無需被每一個用戶或者請求所定制的時候它工作的十分良好
這篇文章對上面描述的技術做了改進通過使用JSP 表達式語言(EL)允許JSP頁面為每一個請求和用戶定制緩存內容緩存頁面片段可以包含未被JSP容器賦值的JSP表達式在每一次頁面被執行時由自定義標簽來確定這些表達式的值因此動態內容的建立被最優化但是緩存片段可以含有部分由每一個請求使用本機JSP表達式語言產生的內容通過JSP 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變量正如下例所示
緩存頁面片段用下列語句輸出結果
${applicationScopecachedFragment}
當緩存片段需要被每一個請求所定制的時候到底發生了什麼?
例如如果希望包含一個計數器需要緩存兩個片段
可以使用下面語句輸出緩存內容
${cachedFragment} ${counter} ${cachedFragment} 通過專門的標簽庫的幫助需要定制的頁面片段的緩存變得異常容易了上面已經提及緩存內容可以被開始標簽()和結尾標簽()封裝起來而每一個定制可以使用另一個標簽()輸出一個JSP表達式(${})來表現動態內容用JSP表達式緩存並在每一次緩存內容被輸出時賦值在下面的部分可以看到這是如何實現的Counterjsp緩存了一個包含計數器的頁面片段當每一次用戶刷新這個頁面的時候計數器會自動+
JSP 變量易於使用對於簡單的Web apps這是一個不錯的內容緩存方案然而如果應用程序產生大量的動態內容沒有對緩存大小的控制無疑是一個問題一種專用的緩存框架結構能夠提供一個更加有力的方案允許對緩存的監視限制緩存大小控制緩存策略等等……使用JSP 表達式語言APIJSP容器(例如Tomcat)對應用EL API的JSP頁面中的表達式予以賦值並且可以被Java代碼所使用這允許在Web頁面外應用JSP EL作開發例如對XML文件基於文本的資源以及自定義腳本當需要控制何時對Web頁面中的表達式進行賦值或者書寫與之相關的表達式時EL API同樣是有用的例如緩存頁面片段可以包含自定義JSP表達式並且當每一次緩存內容被輸出時EL API將用來給這些表達式賦值或者重新賦值文章提供了一個例子程序(參見文末資源部分)這個應用程序包含了一個Java類(JspUtils)和類中包含一個方法eval()這個方法有三個參數JSP表達式表達式的期望類型和一個JSP context對象Eval()方法從JSP context中取得ExpressionEvaluator並且調用evaluate()方法通過表達式表達式的期望類型和一個從JSP congtext中獲得的變量JspUtilseval()方法返回表達式的值package comdevspherearticlesjspcache;
import javaxservletjspJspContext;
import javaxservletjspJspException;
import javaxservletjspPageContext;
import javaxservletjspelELException;
import javaxservletjspelExpressionEvaluator;
import javaioIOException;public class JspUtils
{
public static Object eval( String expr Class type JspContext jspContext)
throws JspException
{
try
{
if (exprindexOf("${") == ) return expr;
ExpressionEvaluator evaluator= jspContextgetExpressionEvaluator();
return evaluatorevaluate(expr type
jspContextgetVariableResolver() null);
} catch (ELException e)
{
throw new JspException(e);
}
}
}注意JspUtilseval()主要封裝了標准的ExpressionEvaluator如果expr不包含${JSP EL API不被調用因為沒有JSP表達式創建標簽庫描述符(TLD)文件JSP標簽庫需要一個標簽庫描述符(TLD)文件來自定義標簽的命名它們的屬性以及操作該標簽的Java類jspcachetld描述了兩個自定義標簽擁有兩個屬性緩存頁面片段的id和JSP scope—JSP頁面總需要被儲存的內容范圍只有一個屬性即JSP表達式必須在每一次緩存片段被輸出時被賦值TLD文件將這兩個自定義標簽映射到CacheTag和DynamicTag類如下所示
TLD文件包含在Web應用描述符文件(webxml)中這五個文件同樣包含一個初始參數指出cache是否可用
理解的工作機理JSP容器為JSP頁面中的每一個標簽創建一個CacheTag實例來對其處理JSP容器負責調用setJsp()setParent()和setJspBody()方法這是CacheTag類從SimpleTagSupport繼承而來JSP容器同事還為所操作標簽的每一個屬性調用setter方法SetId()和setScope()方法存儲屬性值到私有域這個值已經用CacheTag()構造函數用缺省值初始化package comdevspherearticlesjspcache;
xmlns:xsi="
xsi:schemaLocation="
version="">
comdevspherearticlesjspcacheenabled
import javaxservletServletContext;
import javaxservletjspJspContext;
import javaxservletjspJspException;
import javaxservletjspPageContext;
import javaxservletjsptagextSimpleTagSupport;
import javaioIOException;import javaioStringWriter;
public class CacheTag extends SimpleTagSupport
{
public static final String CACHE_ENABLED =
"comdevspherearticlesjspcacheenabled";
private String id; private int scope;
private boolean cacheEnabled; public CacheTag()
{
id = null;scope = PageContextAPPLICATION_SCOPE;
}public void setId(String id)
{
thisid = id;
}
public void setScope(String scope)
{
thisscope = JspUtilscheckScope(scope);
}
}
setScope()方法調用JspUtilscheckScope()來校驗已經
從String轉換為int類型的scope的屬性值 public class JspUtils { public static int checkScope(String scope) { if ("page"equalsIgnoreCase(scope)) return PageContextPAGE_SCOPE; else if ("request"equalsIgnoreCase(scope)) return PageContextREQUEST_SCOPE; else if ("session"equalsIgnoreCase(scope)) return PageContextSESSION_SCOPE; else if ("application"equalsIgnoreCase(scope)) return PageContextAPPLICATION_SCOPE; else throw new IllegalArgumentException ( "Invalid scope: " + scope); }}一旦CacheTag實例准備對標簽進行操作JSP容器調用doTag()方法用getJspContext()來獲得JSP context這個對象被造型為PageContext從而可以調用getServletContext()方法servlet context用來獲取初始化參數的值這個值標明緩存機制是否被啟用如果緩存被啟用doTag()嘗試使用id和scope屬性值來獲得緩存頁面片段如果頁面片段還沒有被緩存doTag()使用getJspBody()invoke()來執行由和封裝的JSP代碼由JSP body產生的輸出結果緩沖在StringWriter並且被toStirng()方法獲得這樣doTag()調用JSP context的setAttribute()方法新建一個JSP變量這個變量控制可能包含JSP表達式(${…})的緩存內容這些表達式在用jspContextgetOut()print()輸出內容前被JspUtilseval()賦值只有當緩存被啟用的時候這些行為才發生否則doTag()只是通過getJspBody()invoke(null)執行JSP body並且輸出結果不被緩存
public class CacheTag extends SimpleTagSupport
{
public void doTag() throws JspException IOException
{
JspContext jspContext = getJspContext();
ServletContext application = ((PageContext)
jspContext)getServletContext();
String cacheEnabledParam= applicationgetInitParameter(CACHE_ENABLED);
cacheEnabled = cacheEnabledParam != null
&& cacheEnabledParamequals("true");
if (cacheEnabled)
{
String cachedOutput= (String) jspContextgetAttribute(id scope);
if (cachedOutput == null)
{
StringWriter buffer = new StringWriter();
getJspBody()invoke(buffer);
cachedOutput = buffertoString();
jspContextsetAttribute(id cachedOutput scope);
}
String evaluatedOutput = (String)
JspUtilseval( cachedOutput Stringclass jspContext);
jspContextgetOut()print(evaluatedOutput);
}
else getJspBody()invoke(null);
}
}注意一個單獨的JspUtilseval()調用給所有的${…} 表達式賦值因為一個包含了大量的${…}結構的text也是一個表達式每一個緩存片段都可以被當作一個復雜的JSP表達式來進行處理 IsCacheEnabled()方法返回cacheEnabled的值這個值已經被doTag()初始化IsCacheEnabled()方法返回cacheEnabled的值這個值已經被doTag()初始化
public class CacheTag extends SimpleTagSupport
{
public boolean isCacheEnabled() { return cacheEnabled;
}
}標簽允許頁面開發者自主選擇緩存頁面片段的ID這使得緩存一個頁面片段可以被多個JSP頁面共享當需要重用JSP代碼時這是很有用處的但是仍然需要一些命名協議來避免可能的沖突通過修改CacheTag類來在自動ID內部包含URL可以避免這種副作用 理解在做什麼每一個被一個DynamicTag類的實例處理setExpr()方法將expr屬性值存儲到一個私有域DoTag()方法創建JSP表達式在expr屬性值加上${前綴和}後綴然後doTag()使用findAncestorWithClass()來查找含有標簽元素的的CacheTag handler如果沒有查找到或者緩存被禁用JSP表達式被JspUtilseval()賦值並且值被輸出否則doTag()輸出無值表達式package comdevspherearticlesjspcache;
import javaxservletjspJspException;
import javaxservletjsptagextSimpleTagSupport;
import javaioIOException;
public class DynamicTag extends SimpleTagSupport
{
private String expr; public void setExpr(String expr)
{
thisexpr = expr;
}
public void doTag() throws JspException IOException
{
String output = "${" + expr + "}";
CacheTag ancestor = (CacheTag) findAncestorWithClass
( this CacheTagclass);
if (ancestor == null || !ancestorisCacheEnabled())
output = (String) JspUtilseval
( output Stringclass getJspContext());
getJspContext()getOut()print(output);
}
}分析上面的代碼大家可以注意到和合作來實現一個盡可能有效率的方案如果緩存可用頁面片段和由生成並被CacheTag賦值的JSP表達式一起放入緩沖器如果緩存被禁用緩沖變得沒有意義只是執行其JSP body部分而讓DynamicTag給JSP表達式賦值禁用緩存有時候是必要的特別是在開發過程期間出現內容的改變和JSP頁面被重新編譯的時候當然在開發完畢的成品環境中緩存必須被啟用總結內容緩存是一種非常易用的改善Web應用性能的方法這篇文章集中討論了使用JSP表達式語言來為每一個用戶或者請求定制緩存內容貫穿全文的簡單介紹的標簽庫適合小型Web apps並且可以提升中等應用的性能對於開發大型企業級應用則應當考慮使用支持更好的緩存機制的框架結構而不僅僅局限於使用JSP變量
From:http://tw.wingwit.com/Article/program/Java/Javascript/201311/25492.html