程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> JSP編程 >> 關於JSP >> 深入講解JSP 2.0下的動態內容緩存技術

深入講解JSP 2.0下的動態內容緩存技術

日期:2017/1/9 14:17:52      編輯:關於JSP

      內容緩存是Web應用中最普通的優化技術之一,例如,可以使用一個自定義地JSP標簽——我們將之命名為——由將每一個需要被緩存的頁面片段封裝起來。任何自定義標簽可以控制它所包含部分 (也即預先封裝的頁面片段)在何時執行,並且動態輸出結果可以被捕獲。標簽使得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變量正如下例所示:

      

      

      

      ...

      

      緩存頁面片段用下列語句輸出結果:

      ${applicationScope.cachedFragment}

      當緩存片段需要被每一個請求所定制的時候,到底發生了什麼?

      例如,如果希望包含一個計數器,需要緩存兩個片段:

      

      

      

      

      

      

      ...

      

      

      ...

      

      可以使用下面語句輸出緩存內容:

      ${cachedFragment1} ${counter} ${cachedFragment2} 通過專門的標簽庫的幫助,需要定制的頁面片段的緩存變得異常容易了。上面已經提及,緩存內容可以被開始標簽()和結尾標簽()封裝起來。而每一個定制可以使用另一個標簽()輸出一個JSP表達式(${...})來表現。動態內容用JSP表達式緩存並在每一次緩存內容被輸出時賦值。在下面的部分可以看到這是如何實現的。Counter.jsp緩存了一個包含計數器的頁面片段,當每一次用戶刷新這個頁面的時候計數器會自動+1。

      

      

      

      

      JSP 變量易於使用,對於簡單的Web apps,這是一個不錯的內容緩存方案。然而,如果應用程序產生大量的動態內容,沒有對緩存大小的控制無疑是一個問題。一種專用的緩存框架結構能夠提供一個更加有力的方案,允許對緩存的監視,限制緩存大小,控制緩存策略,等等……使用JSP 2.0表達式語言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中獲得的變量。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描述了兩個自定義標簽,擁有兩個屬性:緩存頁面片段的id和JSP scope—JSP頁面總需要被儲存的內容范圍。只有一個屬性,即JSP表達式必須在每一次緩存片段被輸出時被賦值。TLD文件將這兩個自定義標簽映射到CacheTag和DynamicTag類,如下所示:

      TLD文件包含在Web應用描述符文件(web.xml)中,這五個文件同樣包含一個初始參數指出cache是否可用。

      理解的工作機理JSP容器為JSP頁面中的每一個標簽創建一個CacheTag實例,來對其處理。JSP容器負責調用setJsp()、setParent()和setJspBody()方法,這是CacheTag類從SimpleTagSupport繼承而來。JSP容器同事還為所操作標簽的每一個屬性調用setter方法。SetId()和setScope()方法存儲屬性值到私有域,這個值已經用CacheTag()構造函數用缺省值初始化。package com.devsphere.articles.jspcache;

      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()來執行由封裝的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()初始化。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表達式被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);

      }

      }分析上面的代碼,大家可以注意到合作來實現一個盡可能有效率的方案。如果緩存可用,頁面片段和由生成並被CacheTag賦值的JSP表達式一起放入緩沖器。如果緩存被禁用,緩沖變得沒有意義,只是執行其JSP body部分,而讓DynamicTag給JSP表達式賦值。禁用緩存有時候是必要的,特別是在開發過程期間出現內容的改變和JSP頁面被重新編譯的時候。當然,在開發完畢的成品環境中緩存必須被啟用。總結內容緩存是一種非常易用的改善Web應用性能的方法。這篇文章集中討論了使用JSP表達式語言來為每一個用戶或者請求定制緩存內容。貫穿全文的簡單介紹的標簽庫適合小型Web apps並且可以提升中等應用的性能。對於開發大型企業級應用,則應當考慮使用支持更好的緩存機制的框架結構,而不僅僅局限於使用JSP變量。

    1. 上一頁:
    2. 下一頁:
    Copyright © 程式師世界 All Rights Reserved