JSP 2.0下的動態內容緩衝分析講解

來源:互聯網
上載者:User

在Web應用中,內容緩衝是最普通的最佳化技術之一,並且能夠很容易地實現。例如,可以使用一個自訂地JSP標籤——我們將之命名為<jc:cache>——由<jc:cache>和</jc:cache>將每一個需要被緩衝的頁面片段封裝起來。任何自訂標籤可以控制它所包含部分 (也即預先封裝的頁面片段)在何時執行,並且動態輸出結果可以被捕獲。<jc:cache>標籤使得JSP容器(例如Tomcat)只產生內容一次,作為應用程式範圍內的JSP變數,來儲存每一個緩衝片段。每次JSP頁面被執行時,自訂標籤將快取頁面面片段載入而無需再次執行JSP代碼來產生輸出結果。作為Jakarta工程的一個部分,標籤庫的開發使用了這項技術。當被緩衝內容無需被每一個使用者或者請求所定製的時候,它工作的十分良好。
  
  這篇文章對上面描述的技術做了改進,通過使用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變數正如下例所示:
  
  <%@ 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}
  
  當緩衝片段需要被每一個請求所定製的時候,到底發生了什嗎?例如,如果希望包含一個計數器,需要緩衝兩個片段:
  <%@ 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}
  
  通過專門的標籤庫的協助,需要定製的頁面片段的緩衝變得異常容易了。上面已經提及,緩衝內容可以被開始標籤(<jc:cache>)和結尾標籤(</jc:cache>)封裝起來。而每一個定製可以使用另一個標籤(<jc:dynamic expr="..."/>)輸出一個JSP運算式(${...})來表現。動態內容用JSP運算式緩衝並在每一次緩衝內容被輸出時賦值。在下面的部分可以看到這是如何?的。Counter.jsp緩衝了一個包含計數器的頁面片段,當每一次使用者重新整理這個頁面的時候計數器會自動+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,這是一個不錯的內容緩衝方案。然而,如果應用程式產生大量的動態內容,沒有對緩衝大小的控制無疑是一個問題。一種專用的緩衝架構結構能夠提供一個更加有力的方案,允許對緩衝的監視,限制緩衝大小,控制緩衝策略,等等……
  
  使用JSP 2.0運算式語言API
  
  JSP容器(例如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中獲得的變數。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運算式。

建立標籤庫描述符(TLD)檔案
  
  JSP標籤庫需要一個標籤庫描述符(TLD)檔案來自訂標籤的命名,它們的屬性,以及操作該標籤的Java類。jspcache.tld描述了兩個自訂標籤,<jc:cache>擁有兩個屬性:快取頁面面片段的id和JSP scope—JSP頁面總需要被儲存的內容約制。<jc:dynamic>只有一個屬性,即JSP運算式必須在每一次緩衝片段被輸出時被賦值。TLD檔案將這兩個自訂標籤映射到CacheTag和DynamicTag類,如下所示:
  <?xml version="1.0" encoding="UTF-8" ?><taglib xmlns="http://java.sun.com/xml/ns/j2ee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_0.xsd"
  version="2.0">
  <tlib-version>1.0</tlib-version>
  <short-name>jc</short-name>
  <uri>http://devsphere.com/articles/jspcache</uri>
  <tag>
  <name>cache</name>
  <tag-class>com.devsphere.articles.jspcache.CacheTag</tag-class>
  <body-content>scriptless</body-content>
  <attribute>
  <name>id</name>
  <required>true</required>
  <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
  <name>scope</name>
  <required>false</required>
  <rtexprvalue>false</rtexprvalue>
  </attribute>
  </tag>
  <tag>
  <name>dynamic</name>
  <tag-class>com.devsphere.articles.jspcache.DynamicTag</tag-class>
  <body-content>empty</body-content>
  <attribute>
  <name>expr</name>
  <required>true</required>
  <rtexprvalue>false</rtexprvalue>
  </attribute>
  </tag></taglib>
  
  TLD檔案包含在Web應用描述符檔案(web.xml)中,這五個檔案同樣包含一個初始參數指出cache是否可用。
  <?xml version="1.0" encoding="ISO-8859-1"?><web-app xmlns="http://java.sun.com/xml/ns/j2ee"
  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">
  <context-param>
  <param-name>com.devsphere.articles.jspcache.enabled</param-name>
  <param-value>true</param-value>
  </context-param>
  <jsp-config>
  <taglib>
  <taglib-uri>
http://devsphere.com/articles/jspcache</taglib-uri>
  <taglib-location>/WEB-INF/jspcache.tld</taglib-location>
  </taglib>
  </jsp-config></web-app>
  
  理解<jc:cache>的工作機理
  
  JSP容器為JSP頁面中的每一個<jc:cache>標籤建立一個CacheTag執行個體,來對其處理。JSP容器負責調用setJsp()、setParent()和setJspBody()方法,這是CacheTag類從SimpleTagSupport繼承而來。JSP容器同事還為所操作標籤的每一個屬性調用setter方法。SetId()和setScope()方法儲存屬性值到私人域,這個值已經用CacheTag()建構函式用預設值初始化。
  
  package com.devsphere.articles.jspcache;
  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()來執行由<jc:cache>和</jc:cache>封裝的JSP代碼。由JSP body產生的輸出結果緩衝在StringWriter並且被toStirng()方法獲得。這樣,doTag()調用JSP context的setAttribute()方法建立一個JSP變數,這個變數控制可能包含JSP運算式(${…})的緩衝內容。這些運算式在用jspContext.getOut().print()輸出內容前,被JspUtils.eval()賦值。只有當緩衝被啟用的時候,這些行為才發生。否則,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
  = 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()初始化。
  ...public class CacheTag extends SimpleTagSupport {
  ...  public boolean isCacheEnabled() {
  return cacheEnabled;
  }}
  
  <jc:cache>標籤允許頁面開發人員自主選擇快取頁面面片段的ID。這使得緩衝一個頁面片段可以被多個JSP頁面共用,當需要重用JSP代碼時,這是很有用處的。但是仍然需要一些命名協議來避免可能的衝突。通過修改CacheTag類來在自動ID內部包含URL可以避免這種副作用。
  
  理解<jc:dynamic>在做什麼
  
  每一個<jc:dynamic>被一個DynamicTag類的執行個體處理,setExpr()方法將expr屬性值儲存到一個私人域。DoTag()方法建立JSP運算式,在expr屬性值加上${首碼和}尾碼。然後,doTag()使用findAncestorWithClass()來尋找含有<jc:dynamic>標籤元素的<jc:cache>的CacheTag handler。如果沒有尋找到或者緩衝被禁用,JSP運算式被JspUtils.eval()賦值並且值被輸出。否則,doTag()輸出無值運算式。
  
  package com.devsphere.articles.jspcache;
  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);
  }}
  
  分析以上代碼,可以注意到<jc:cache>和<jc:dynamic>合作來實現一個儘可能有效率的方案。如果緩衝可用,頁面片段和由<jc:dynamic>產生並被CacheTag賦值的JSP運算式一起放入緩衝器。如果緩衝被禁用,緩衝變得沒有意義,<jc:cache>只是執行其JSP body部分,而讓DynamicTag給JSP運算式賦值。禁用緩衝有時候是必要的,特別是在開發過程期間出現內容的改變和JSP頁面被重新編譯的時候。當然,在開發完畢的成品環境中緩衝必須被啟用。
  
  總結
  
  內容緩衝是一種非常易用的改善Web應用效能的方法。這篇文章集中討論了使用JSP運算式語言來為每一個使用者或者請求定製緩衝內容。貫穿全文的簡單介紹的標籤庫適合小型Web apps並且可以提升中等應用的效能。對於開發大型企業級應用,則該考慮使用支援更好的緩衝機制的架構結構,而不僅是使用JSP變數。但是瞭解基於EL API的定製技術無疑是不無裨益的。



相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.