| 導購 | 订阅 | 在线投稿
分享
 
 
 

Template和JSP技術

2007-02-01 20:11:14  編輯來源:互聯網  简体版  手機版  評論  字體: ||
 
  (本文發于java emag第一期)一、起源與現狀:
  關于Template和JSP的起源還要追述到Web開發的遠古年代,那個時候的人們用CGI來開發web應用,在一個CGI程序中寫HTML標簽。
  在這之後世界開始朝不同的方向發展:sun公司提供了類似于CGI的servlet解決方案,但是無論是CGI還是servlet都面對同一個問題:在程序裏寫html標簽,無論如何都不是一個明智的解決方案。于是sun公司于1999年推出了JSP技術。而在另一個世界裏,以PHP和ASP爲代表的scriptlet頁面腳本技術開始廣泛應用。
  不過即便如此,問題並沒有結束,新的問題出現了:業務和HTML標簽的混合,這個問題不僅導致頁面結構的混亂,同時也使代碼本身難以維護。
  于是來自起源于70年代後期的MVC模式被引入開發。MVC的三個角色:Model——包含除UI的數據和行爲的所有數據和行爲。View是表示UI中模型的顯示。任何信息的變化都由MVC中的第三個成員來處理——控制器。
  在之後的應用中,出現了技術的第一次飛躍:前端的顯示邏輯和後端的業務邏輯分離,COM組件或EJB或CORBA用于處理業務邏輯,ASP、JSP以及PHP被用于前端的顯示。這個就是Web開發的Model 1階段(頁面控制器模式)。
  不過這個開發模式有很多問題:
  1. 頁面中必須寫入Scriptlet調用組件以獲得所必需的數據。
  2. 處理顯示邏輯上Scriptlet代碼和HTML代碼混合交錯。
  3. 調試困難。JSP被編譯成servlet,頁面上的調試信息不足以定位錯誤。
  這一切都是因爲在Model 1中並沒有分離視圖和控制器。完全分離視圖和控制器就成了必須。這就是Model 2。它把Model 1中未解決的問題——分離對組件(業務邏輯)的調用工作,把這部分工作移植到了控制器。現在似乎完美了,不過等等,原來的控制器從頁面中分離後,頁面所需的數據怎麽獲得,誰來處理頁面顯示邏輯?兩個辦法:1. 繼續利用asp,php或者jsp等機制,不過由于它們是運行在web環境下的,他們所要顯示的數據(後端邏輯産生的結果)就需要通過控制器放入request流中;2. 使用新手法——模板技術,使用獨立的模板技術由于脫離的了web環境,會給開發測試帶來相當的便利。至于頁面所需數據傳入一個POJO就行而不是request對象。
  模板技術最先開始于PHP的世界,出現了PHPLIB Template和FastTemplate這兩位英雄。不久模板技術就被引入到java web開發世界裏。目前比較流行的模板技術有:XSTL,Velocity,JDynamiTe,Tapestry等。另外因爲JSP技術畢竟是目前標准,相當的系統還是利用JSP來完成頁面顯示邏輯部分,在Sun公司的JSTL外,各個第三方組織也紛紛推出了自己的Taglib,一個代表是struts tablib。
  二、 模板技術分析:
  模板技術從本質上來講,它是一個占位符動態替換技術。一個完整的模板技術需要四個元素:0. 模板語言,1. 包含模板語言的模板文件,2. 擁有動態數據的數據對象,3. 模板引擎。以下就具體討論這四個元素。(在討論過程中,我只列舉了幾個不同特點技術,其它技術或有雷同就不重複了)
  1. 模板語言:
  模板語言包括:變量標識和表達式語句。根據表達式的控制力不同,可以分爲強控制力模板語言和弱控制力模板語言。而根據模板語言與HTML的兼容性不同,又可以分爲兼容性模板語言和非兼容性模板語言。
  模板語言要處理三個要點:
  1. 標量標記。把變量標識插入html的方法很多。其中一種是使用類似html的標簽;另一種是使用特殊標識,如Velocity或者JDynamiTe;第三種是擴展html標簽,如tapestry。采用何種方式有著很多考慮,一個比較常見的考慮是“所見即所得”的要求。
  2. 條件控制。這是一個很棘手的問題。一個簡單的例子是某物流陪送系統中,物品數低于一定值的要高亮顯示。不過對于一個具體複雜顯示邏輯的情況,條件控制似乎不可避免。當你把類似于<IF condition=”$count <= 40”><then><span class=”highlight”>count </span></then></IF>引入,就象我們當初在ASP和PHP中所做得一樣,我們將不得不再一次面對scriptlet嵌入網頁所遇到的問題。我相信你和我一樣並不認爲這是一個好得的編寫方式。實際上並非所有的模板技術都使用條件控制,很多已有的應用如PHP上中的以及我曾見過一個基于ASP.NET的應用,當然還有Java的JDynamiTe。這樣網頁上沒有任何邏輯,不過這樣做的代價是把高亮顯示的選擇控制移交給編程代碼。你必需做個選擇。也許你也象我一樣既不想在網頁中使用條件控制,也不想在代碼中寫html標記,但是這個顯示邏輯是無可逃避的(如果你不想被你的老板抄鱿魚的話),一個可行的方法是用CSS,在編程代碼中決定采用哪個css樣式。特別是CSS2技術,其selector機制,可以根據html類型甚至是element的attributes來apply不同的樣式。
  3. 叠代(循環)。在網頁上顯示一個數據表單是一個很基本的要求,使用集合標簽將不可避免,不過幸運的是,它通常很簡單,而且夠用。特別值得一提的是PHP的模板技術和JDynamiTe技術利用html的注釋標簽很簡單的實現了它,又保持了“所見既所得”的特性。
  下面是一些技術的比較:
  
  
  
   Velocity
  
  
  
  
   變量定義:用$標志
   表達式語句:以#開始
   強控制語言:變量賦值:#set $this = "Velocity"
   外部引用:#include ( $1 )
   條件控制:#if …. #end
   非兼容語言
  
  
  
  
   JDynamiTe
   變量定義:用{}包裝
   表達式語句:寫在注釋格式(<!-- à)中
   弱控制語言
   兼容語言
  
  
  
  
   XSLT
   變量定義:xml標簽
   表達式:xsl標簽
   強控制語言:外部引用:import,include
   條件控制:if, choose…when…otherwise
   非兼容語言
  
  
  
  
   Tapestry
   采用component的形式開發。
   變量定義(組件定義):在html標簽中加上jwcid
   表達式語句:ognl規範
   兼容語言
  
  
  
  2. 模板文件:
  模板文件指包含了模板語言的文本文件。
  模板文件由于其模板語言的兼容性導致不同結果。與HTML兼容性的模板文件只是一個資源文件,其具有良好的複用性和維護性。例如JDynamiTe的模板文件不但可以在不同的項目中複用,甚至可以和PHP程序的模板文件互用。而如velocity的非兼容模板文件,由于其事實上是一個腳本程序,複用性和可維護性大大降低。
  3. 擁有動態數據的數據對象:
  模板文件包含的是靜態內容,那麽其所需的動態數據就需要另外提供。根據提供數據方式的不同可以分爲3種:
  1. Map:利用key/value來定位。這個是最常見的技術。如velocity的VelocityContext就是包含了map對象。
  
  
  
   Example.vm:
   Hello from $name in the $project project.
  
   Example.java:
   VelocityContext context = new VelocityContext();
   context.put("name", "Velocity");
   context.put("project", "Jakarta");
  
  
  
  2. DOM:直接操作DOM數據對象,如XSLT利用XPath技術。
  3. POJO:直接利用反射取得DTO對象,利用JavaBean機制取得數據。如Tapestry。
  4. 模板引擎:
  模板引擎的工作分爲三步:
  1. 取得模板文件並確認其中的模板語言符合規範。
  比如velocity,確定#if有對應得#end等。Xml+xslt的模型中,xml文件標簽是否完整等。在完成這些工作後,模板引擎通常會把模板文件解析成一顆節點樹(包含模板文件的靜態內容節點和模板引擎所定義的特殊節點)。
  2. 取得數據對象。
   該數據對象一般通過程序傳遞引用實現。現有的大量框架在程序底層完成,處理方式也各自不同,有兩種技術分別爲推技術和拉技術。推技術:controller調用set方法把動態數據注入,模板引擎通過get方法獲得,典型代表:Struts;拉技術:模板引擎根據配置信息,找到與view對應的model,調用model的get方法取得數據,典型代表:Tapestry。
  3. 合並模板文件(靜態內容)和數據對象(動態內容),並生成最終頁面。
   合並的機制一般如下,模板引擎遍曆這顆節點樹的每一個節點,並render該節點,遇到靜態內容節點按正常輸入,遇到特殊節點就從數據對象中去得對應值,並執行其表達式語句(如果有的話)。
  以下詳細說明:
  
  
  
   Velocity
  
  
  
  
   Template template = Velocity.getTemplate("test.wm");
   Context context = new VelocityContext();
   context.put("foo", "bar");
   context.put("customer", new Customer());
   template.merge(context, writer);
   當調用Velocity.getTemplate 方法時,將調用ResourceManger的對應方法。
   ResourceManger先查看該模板文件是否在cache中,如果沒有就去獲取,生成resource對象並調用process()方法,確定該模板是否有效,如果有效,則在內存中生成一個Node樹。
   當調用template.merge()時,遍曆這顆Node樹,並調用每個Node的render方法。對于模板中的變量和對象Node,還將調用execute()方法,從context中取得value。
   注:ResourceManger在runtime\resource包下,Node在runtime\parser\node包下
  
  
  
  
   Tapestry
  
  
  
  
   Tapestry比較麻煩,先介紹一下http請求的處理過程。
   當httprequest請求到達時。該請求被ApplicationServlet捕獲,隨後ApplicationServlet通過getEngine取到對應的Engine,通過該engine的getService拿到對應的service,調用其service方法執行http請求。
   每個service通過RequestCycle對象的getPage方法取得Page對象,並將其設置爲該Cycle對象的active Page。之後service調用renderResponse方法執行輸出。
   renderResponse調用page的getResponseWriter(output)取得writer對象,並把它傳給cycle.renderPage(writer)方法,該方法調用page的renderPage方法。
   Page執行renderPage時,首先判斷是否有listener的請求,如果有則處理listener請求;然後調用BaseComponentTemplateLoader的process方法把模板文件載入並形成一個component節點樹,依次執行節點的renderComponent方法。
   每個component對象將通過ongl的機制取得對象屬性。並把該值寫入輸入流。
   例如:insert component
   protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle) {
   if (cycle.isRewinding())
   return;
   Object value = getValue();
   if (value == null)
   return;
   String insert = null;
   Format format = getFormat();
   if (format == null) {
   insert = value.toString();
   }
   else{
   try{
   insert = format.format(value);
   }
   catch (Exception ex) {
   throw new ApplicationRuntimeException(
   Tapestry.format("Insert.unable-to-format",value),this, getFormatBinding().getLocation(), ex);
   }
   }
   String styleClass = getStyleClass();
   if (styleClass != null) {
   writer.begin("span");
   writer.attribute("class", styleClass);
   renderInformalParameters(writer, cycle);
   }
   if (getRaw())
   writer.printRaw(insert);
   else
   writer.print(insert);
   if (styleClass != null)
   writer.end(); // <span>
   }
   getValue爲取得insert的value屬性。
  
  
  
  三、JSP技術分析
   1. JSP技術:
   JSP,一個僞裝後的servlet。web server會對任何一個jsp都生成一個對應jsp類,打開這個類,就會發現,jsp提供的是一個代碼生成機制,把jsp文件中所有的scriptlet原封不動的copy的到生成的jsp類中,同時調用println把所有的html標簽輸出。
  
  
  
   Test.jsp:
   <html>
   <head><title>jsp test</title></head>
   <body>
   <table width="226" border="0" cellspacing="0" cellpadding="0">
   <tr><td><font face="Arial" size="2" color="#000066">
   <b class="headlinebold">The jsp test file</b>
   </tr></td> </font>
   </table>
   <body>
   </html>
  
  
  
  
   Test_jsp.java:
   package org.apache.jsp;
   import javax.servlet.*;
   import javax.servlet.http.*;
   import javax.servlet.jsp.*;
   import org.apache.jasper.runtime.*;
  
   public class Test _jsp extends HttpJspBase {
   private static java.util.Vector _jspx_includes;
   public java.util.List getIncludes() {
   return _jspx_includes;
   }
   public void _jspService(HttpServletRequest request, HttpServletResponse response)
   throws java.io.IOException, ServletException {
   JspFactory _jspxFactory = null;
   javax.servlet.jsp.PageContext pageContext = null;
   HttpSession session = null;
   ServletContext application = null;
   ServletConfig config = null;
   JspWriter out = null;
   Object page = this;
   JspWriter _jspx_out = null;
  
   try {
   _jspxFactory = JspFactory.getDefaultFactory();
   response.setContentType("text/html;charset=ISO-8859-1");
   pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true);
   application = pageContext.getServletContext();
   config = pageContext.getServletConfig();
   session = pageContext.getSession();
   out = pageContext.getOut();
   _jspx_out = out;
  
   out.write("<html>\r\n");
   out.write("<head><title>jsp test</title></head> \r\n");
   out.write("<body>\r\n");
   out.write("<table width=\"226\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\r\n ");
   out.write("<tr><td><font face=\"Arial \" size=\"2\" color=\"#000066\"> \r\n\t ");
   out.write("<b class=\"headlinebold\">The jsp test file");
   out.write("</b>\r\n\t ");
   out.write("</tr></td></font>\r\n\t ");
   out.write("</table>\r\n");
   out.write("<body>\r\n");
   out.write("</html>");
   } catch (Throwable t) {
   out = _jspx_out;
   if (out != null && out.getBufferSize() != 0)
   out.clearBuffer();
   if (pageContext != null) pageContext.handlePageException(t);
   } finally {
   if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);
   }
   }
   }
  
  
  
   2. Taglib技術:
  Taglib作爲jsp之上的輔助技術,其工作本質依托與jsp技術,也是自定義標簽翻譯成java代碼,不過這次和jsp略有不同,它還要經過幾個過程。
  先來看一下,實現一個tag的2個要點:
  1. 提供屬性的set方法,此後這個屬性就可以在jsp頁面設置。以jstl標簽爲例 c:out value=""/,這個value就是jsp數據到tag之間的入口。所以tag裏面必須有一個setValue方法,具體的屬性可以不叫value。例如setValue(String data){this.data = data;}。這個“value”的名稱是在tld裏定義的。取什麽名字都可以,只需tag裏提供相應的set方法即可。
  2. 處理 doStartTag 或 doEndTag 。這兩個方法是 TagSupport提供的。還是以c:out value=""/爲例,當jsp解析這個標簽的時候,在“<”處觸發 doStartTag 事件,在“>”時觸發 doEndTag 事件。通常在 doStartTag 裏進行邏輯操作,在 doEndTag 裏控制輸出。
   在處理tag的時候:
   0. 從tagPool中取得對應tag。
  1. 爲該tag設置頁面上下文。
  2. 爲該tag設置其父tag,如果沒有就爲null。
  3. 調用setter方法傳入標簽屬性值tag,如果該標簽沒有屬性,此步跳過。
  4. 調用doStartTag方法,取的返回值。
  5. 如果該標簽有body,根據doStartTag返回值確定是否pop該標簽內容。如果要pop其body,則:setBodyContent(),在之後,doInitBody()。如果該標簽沒有body,此步跳過。
  6. 調用doEndTag()以確定是否跳過頁面剩下部分。
  7. 最後把tag類返還給tagPool。
  
  
  
   tag類爲:
   package my.customtags;
   import javax.servlet.jsp.JspWriter;
   import javax.servlet.jsp.PageContext;
   import javax.servlet.jsp.tagext.TagSupport;
  
   public class Hidden extends TagSupport{
   String name;
   public Hidden(){ name = ""; }
   public void setName(String name){ this.name = name; }
   public void release(){ value = null; }
   public int doStartTag(){ return EVAL_BODY_INCLUDE;}
   public int doEndTag() throws JspTagException{
   try{ pageContext.getOut().write(", you are welcome"); }
   catch(IOException ex){ throw new JspTagException("Error!"); }
   return EVAL_PAGE;
   }
   }
  
   Jsp頁面:
   <my:hidden name="testname"/>
  
   生成的jsp代碼:
   my.customtags.Hidden _jspx_th_my_hidden_11 = (my.customtags.Hidden) _jspx_tagPool_my_hidden_name.get(my.customtags.Hidden.class);
   _jspx_th_my_hidden_11.setPageContext(pageContext);
   _jspx_th_my_hidden_11.setParent(null);
   _jspx_th_my_hidden_11.setName("testname");
   int _jspx_eval_my_hidden_11 = _jspx_th_my_hidden_11.doStartTag();
   if (_jspx_th_my_hidden_11.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE)
   return true;
   _jspx_tagPool_my_hidden_name.reuse(_jspx_th_my_hidden_11);
   return false;
  
  
  
   Taglib技術提供兩個機制,Body和non-Body導致了taglib的出現了兩個分支:Display Tag和Control Tag, 前者在java code中嵌入了html標簽,相當與一個web component,而後者則是另一種模板腳本。
  四、兩種技術方案的比較:
   1. 技術學習難易度
  模板技術。使用模板技術,第一點就是必須學習模板語言,尤其是強控制的模板語言。于是模板語言本身的友好性變的尤爲重要。以下依據友好性,表現力以及複用性三點爲主基點比較了一下幾種模板技術。
  
  
  
   Velocity:
   Turbine項目(http://jakarta.apache.org/Turbine)采用了velocity技術。
   1. 友好性不夠。理由: 強控制類型,出現頁面顯示控制代碼和html混合。與Html的不兼容,無法所見即所得。遇到大的HTML頁面,從一個 “#if”找到對應的 “#end”也是很痛苦的一件事情。
   2. 表現力強。理由:強控制語言。
   3. 複用性弱。理由:模板腳本和頁面代碼混合。
  
  
  
  
   XSLT
   Cocoon項目(http://cocoon.apache.org/)采用XML + XSLT的方法。CSDN社區也是采用此方案。
   1. 內容和顯示風格分離,這點XSLT做的最好。
   2. 速度慢。理由:XSLT的使用XPath,由于是要解析DOM樹,當XML文件大時,速度很慢。
   3. 友好性不夠。理由:由于沒有HTML文件,根本看不到頁面結構、顯示風格和內容。XSL語法比較難以掌握,由于沒有“所見即所得”編輯工具,學習成本高。
   4. 表現力強。理由:強控制語言。
   5. 複用性弱。理由:xsl標簽和html標簽混合。
  
  
  
  
   JDynamiTe
   1. 表現力中等。理由:弱控制語言。
   2. 友好性強。理由:所見即所得的效果。在模板件中的ignore block在編輯條件下可展示頁面效果,而在運行中不會被輸出。
   3. 複用性強。理由:利用html標簽。
  
  
  
  
   Tapestry
   1. 友好性中等。理由:整個Tapestry頁面文件都是HTML元素。但是由于component會重寫html標簽,其顯示的樣子是否正確,將不預測。
   2. 表現力強。理由:強控制語言。
   3. 複用性強。理由:擴展了HTML元素的定義。
  
  
  
  
  在JSP中大量的使用TagLib,能夠使得JSP的頁面結構良好,更符合XML格式,而且能夠重用一些頁面元素。但TagLib的編譯之後的代碼龐大而雜亂。TabLib很不靈活,能完成的事情很有限。TabLib代碼本身的可重用性受到TagSupport定義的限制,不是很好。 另外是,我不得不承認的一件事是,TagLib的編寫本身不是一件愉快的事情,事實我個人很反對這種開發方式。
   2. 技術使用難易度
   模板技術:模板技術本身脫離了Web環境,可以在不啓動Web server得情況下進行開發和測試,一旦出錯詳細的信息易于錯誤的定位。由于模板引擎的控制,頁面中將只處理顯示邏輯(盡管其可能很複雜)
   JSP技術:工作在Web環境下,開發測試一定要運行web server。此外,一些TagLib能夠産生新的標簽,頁面的最終布局也必須在web環境下才可以確定。測試時出錯信息不明確,特別是TagLib得存在,極不容易定位。由于其本質是程序,很容易在其中寫入業務邏輯,甚至于數據庫連接代碼,造成解耦的不徹底。
  3. 總結
  模板技術更加專注于頁面的顯示邏輯,有效幫助開發人員分離視圖和控制器。在學習,開發和測試都更加容易。
  JSP技術本身是一個早期的技術,本身並沒有提出足夠的方式來分離視圖和控制器。相反,我認爲其本身是鼓勵開發人員不做解耦,因爲在JSP代碼中插入業務邏輯是如此的容易。
 
(本文發于java emag第一期)一、起源與現狀: 關于Template和JSP的起源還要追述到Web開發的遠古年代,那個時候的人們用CGI來開發web應用,在一個CGI程序中寫HTML標簽。 在這之後世界開始朝不同的方向發展:sun公司提供了類似于CGI的servlet解決方案,但是無論是CGI還是servlet都面對同一個問題:在程序裏寫html標簽,無論如何都不是一個明智的解決方案。于是sun公司于1999年推出了JSP技術。而在另一個世界裏,以PHP和ASP爲代表的scriptlet頁面腳本技術開始廣泛應用。 不過即便如此,問題並沒有結束,新的問題出現了:業務和HTML標簽的混合,這個問題不僅導致頁面結構的混亂,同時也使代碼本身難以維護。 于是來自起源于70年代後期的MVC模式被引入開發。MVC的三個角色:Model——包含除UI的數據和行爲的所有數據和行爲。View是表示UI中模型的顯示。任何信息的變化都由MVC中的第三個成員來處理——控制器。 在之後的應用中,出現了技術的第一次飛躍:前端的顯示邏輯和後端的業務邏輯分離,COM組件或EJB或CORBA用于處理業務邏輯,ASP、JSP以及PHP被用于前端的顯示。這個就是Web開發的Model 1階段(頁面控制器模式)。 不過這個開發模式有很多問題: 1. 頁面中必須寫入Scriptlet調用組件以獲得所必需的數據。 2. 處理顯示邏輯上Scriptlet代碼和HTML代碼混合交錯。 3. 調試困難。JSP被編譯成servlet,頁面上的調試信息不足以定位錯誤。 這一切都是因爲在Model 1中並沒有分離視圖和控制器。完全分離視圖和控制器就成了必須。這就是Model 2。它把Model 1中未解決的問題——分離對組件(業務邏輯)的調用工作,把這部分工作移植到了控制器。現在似乎完美了,不過等等,原來的控制器從頁面中分離後,頁面所需的數據怎麽獲得,誰來處理頁面顯示邏輯?兩個辦法:1. 繼續利用asp,php或者jsp等機制,不過由于它們是運行在web環境下的,他們所要顯示的數據(後端邏輯産生的結果)就需要通過控制器放入request流中;2. 使用新手法——模板技術,使用獨立的模板技術由于脫離的了web環境,會給開發測試帶來相當的便利。至于頁面所需數據傳入一個POJO就行而不是request對象。 模板技術最先開始于PHP的世界,出現了PHPLIB Template和FastTemplate這兩位英雄。不久模板技術就被引入到java web開發世界裏。目前比較流行的模板技術有:XSTL,Velocity,JDynamiTe,Tapestry等。另外因爲JSP技術畢竟是目前標准,相當的系統還是利用JSP來完成頁面顯示邏輯部分,在Sun公司的JSTL外,各個第三方組織也紛紛推出了自己的Taglib,一個代表是struts tablib。 二、 模板技術分析: 模板技術從本質上來講,它是一個占位符動態替換技術。一個完整的模板技術需要四個元素:0. 模板語言,1. 包含模板語言的模板文件,2. 擁有動態數據的數據對象,3. 模板引擎。以下就具體討論這四個元素。(在討論過程中,我只列舉了幾個不同特點技術,其它技術或有雷同就不重複了) 1. 模板語言: 模板語言包括:變量標識和表達式語句。根據表達式的控制力不同,可以分爲強控制力模板語言和弱控制力模板語言。而根據模板語言與HTML的兼容性不同,又可以分爲兼容性模板語言和非兼容性模板語言。 模板語言要處理三個要點: 1. 標量標記。把變量標識插入html的方法很多。其中一種是使用類似html的標簽;另一種是使用特殊標識,如Velocity或者JDynamiTe;第三種是擴展html標簽,如tapestry。采用何種方式有著很多考慮,一個比較常見的考慮是“所見即所得”的要求。 2. 條件控制。這是一個很棘手的問題。一個簡單的例子是某物流陪送系統中,物品數低于一定值的要高亮顯示。不過對于一個具體複雜顯示邏輯的情況,條件控制似乎不可避免。當你把類似于<IF condition=”$count <= 40”><then><span class=”highlight”>count </span></then></IF>引入,就象我們當初在ASP和PHP中所做得一樣,我們將不得不再一次面對scriptlet嵌入網頁所遇到的問題。我相信你和我一樣並不認爲這是一個好得的編寫方式。實際上並非所有的模板技術都使用條件控制,很多已有的應用如PHP上中的以及我曾見過一個基于ASP.NET的應用,當然還有Java的JDynamiTe。這樣網頁上沒有任何邏輯,不過這樣做的代價是把高亮顯示的選擇控制移交給編程代碼。你必需做個選擇。也許你也象我一樣既不想在網頁中使用條件控制,也不想在代碼中寫html標記,但是這個顯示邏輯是無可逃避的(如果你不想被你的老板抄鱿魚的話),一個可行的方法是用CSS,在編程代碼中決定采用哪個css樣式。特別是CSS2技術,其selector機制,可以根據html類型甚至是element的attributes來apply不同的樣式。 3. 叠代(循環)。在網頁上顯示一個數據表單是一個很基本的要求,使用集合標簽將不可避免,不過幸運的是,它通常很簡單,而且夠用。特別值得一提的是PHP的模板技術和JDynamiTe技術利用html的注釋標簽很簡單的實現了它,又保持了“所見既所得”的特性。 下面是一些技術的比較: Velocity 變量定義:用$標志 表達式語句:以#開始 強控制語言:變量賦值:#set $this = "Velocity" 外部引用:#include ( $1 ) 條件控制:#if …. #end 非兼容語言 JDynamiTe 變量定義:用{}包裝 表達式語句:寫在注釋格式(<!-- à)中 弱控制語言 兼容語言 XSLT 變量定義:xml標簽 表達式:xsl標簽 強控制語言:外部引用:import,include 條件控制:if, choose…when…otherwise 非兼容語言 Tapestry 采用component的形式開發。 變量定義(組件定義):在html標簽中加上jwcid 表達式語句:ognl規範 兼容語言 2. 模板文件: 模板文件指包含了模板語言的文本文件。 模板文件由于其模板語言的兼容性導致不同結果。與HTML兼容性的模板文件只是一個資源文件,其具有良好的複用性和維護性。例如JDynamiTe的模板文件不但可以在不同的項目中複用,甚至可以和PHP程序的模板文件互用。而如velocity的非兼容模板文件,由于其事實上是一個腳本程序,複用性和可維護性大大降低。 3. 擁有動態數據的數據對象: 模板文件包含的是靜態內容,那麽其所需的動態數據就需要另外提供。根據提供數據方式的不同可以分爲3種: 1. Map:利用key/value來定位。這個是最常見的技術。如velocity的VelocityContext就是包含了map對象。 Example.vm: Hello from $name in the $project project. Example.java: VelocityContext context = new VelocityContext(); context.put("name", "Velocity"); context.put("project", "Jakarta"); 2. DOM:直接操作DOM數據對象,如XSLT利用XPath技術。 3. POJO:直接利用反射取得DTO對象,利用JavaBean機制取得數據。如Tapestry。 4. 模板引擎: 模板引擎的工作分爲三步: 1. 取得模板文件並確認其中的模板語言符合規範。 比如velocity,確定#if有對應得#end等。Xml+xslt的模型中,xml文件標簽是否完整等。在完成這些工作後,模板引擎通常會把模板文件解析成一顆節點樹(包含模板文件的靜態內容節點和模板引擎所定義的特殊節點)。 2. 取得數據對象。 該數據對象一般通過程序傳遞引用實現。現有的大量框架在程序底層完成,處理方式也各自不同,有兩種技術分別爲推技術和拉技術。推技術:controller調用set方法把動態數據注入,模板引擎通過get方法獲得,典型代表:Struts;拉技術:模板引擎根據配置信息,找到與view對應的model,調用model的get方法取得數據,典型代表:Tapestry。 3. 合並模板文件(靜態內容)和數據對象(動態內容),並生成最終頁面。 合並的機制一般如下,模板引擎遍曆這顆節點樹的每一個節點,並render該節點,遇到靜態內容節點按正常輸入,遇到特殊節點就從數據對象中去得對應值,並執行其表達式語句(如果有的話)。 以下詳細說明: Velocity Template template = Velocity.getTemplate("test.wm"); Context context = new VelocityContext(); context.put("foo", "bar"); context.put("customer", new Customer()); template.merge(context, writer); 當調用Velocity.getTemplate 方法時,將調用ResourceManger的對應方法。 ResourceManger先查看該模板文件是否在cache中,如果沒有就去獲取,生成resource對象並調用process()方法,確定該模板是否有效,如果有效,則在內存中生成一個Node樹。 當調用template.merge()時,遍曆這顆Node樹,並調用每個Node的render方法。對于模板中的變量和對象Node,還將調用execute()方法,從context中取得value。 注:ResourceManger在runtime\resource包下,Node在runtime\parser\node包下 Tapestry Tapestry比較麻煩,先介紹一下http請求的處理過程。 當httprequest請求到達時。該請求被ApplicationServlet捕獲,隨後ApplicationServlet通過getEngine取到對應的Engine,通過該engine的getService拿到對應的service,調用其service方法執行http請求。 每個service通過RequestCycle對象的getPage方法取得Page對象,並將其設置爲該Cycle對象的active Page。之後service調用renderResponse方法執行輸出。 renderResponse調用page的getResponseWriter(output)取得writer對象,並把它傳給cycle.renderPage(writer)方法,該方法調用page的renderPage方法。 Page執行renderPage時,首先判斷是否有listener的請求,如果有則處理listener請求;然後調用BaseComponentTemplateLoader的process方法把模板文件載入並形成一個component節點樹,依次執行節點的renderComponent方法。 每個component對象將通過ongl的機制取得對象屬性。並把該值寫入輸入流。 例如:insert component protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle) { if (cycle.isRewinding()) return; Object value = getValue(); if (value == null) return; String insert = null; Format format = getFormat(); if (format == null) { insert = value.toString(); } else{ try{ insert = format.format(value); } catch (Exception ex) { throw new ApplicationRuntimeException( Tapestry.format("Insert.unable-to-format",value),this, getFormatBinding().getLocation(), ex); } } String styleClass = getStyleClass(); if (styleClass != null) { writer.begin("span"); writer.attribute("class", styleClass); renderInformalParameters(writer, cycle); } if (getRaw()) writer.printRaw(insert); else writer.print(insert); if (styleClass != null) writer.end(); // <span> } getValue爲取得insert的value屬性。 三、JSP技術分析 1. JSP技術: JSP,一個僞裝後的servlet。web server會對任何一個jsp都生成一個對應jsp類,打開這個類,就會發現,jsp提供的是一個代碼生成機制,把jsp文件中所有的scriptlet原封不動的copy的到生成的jsp類中,同時調用println把所有的html標簽輸出。 Test.jsp: <html> <head><title>jsp test</title></head> <body> <table width="226" border="0" cellspacing="0" cellpadding="0"> <tr><td><font face="Arial" size="2" color="#000066"> <b class="headlinebold">The jsp test file</b> </tr></td> </font> </table> <body> </html> Test_jsp.java: package org.apache.jsp; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.jsp.*; import org.apache.jasper.runtime.*; public class Test _jsp extends HttpJspBase { private static java.util.Vector _jspx_includes; public java.util.List getIncludes() { return _jspx_includes; } public void _jspService(HttpServletRequest request, HttpServletResponse response) throws java.io.IOException, ServletException { JspFactory _jspxFactory = null; javax.servlet.jsp.PageContext pageContext = null; HttpSession session = null; ServletContext application = null; ServletConfig config = null; JspWriter out = null; Object page = this; JspWriter _jspx_out = null; try { _jspxFactory = JspFactory.getDefaultFactory(); response.setContentType("text/html;charset=ISO-8859-1"); pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true); application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); _jspx_out = out; out.write("<html>\r\n"); out.write("<head><title>jsp test</title></head> \r\n"); out.write("<body>\r\n"); out.write("<table width=\"226\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\r\n "); out.write("<tr><td><font face=\"Arial \" size=\"2\" color=\"#000066\"> \r\n\t "); out.write("<b class=\"headlinebold\">The jsp test file"); out.write("</b>\r\n\t "); out.write("</tr></td></font>\r\n\t "); out.write("</table>\r\n"); out.write("<body>\r\n"); out.write("</html>"); } catch (Throwable t) { out = _jspx_out; if (out != null && out.getBufferSize() != 0) out.clearBuffer(); if (pageContext != null) pageContext.handlePageException(t); } finally { if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext); } } } 2. Taglib技術: Taglib作爲jsp之上的輔助技術,其工作本質依托與jsp技術,也是自定義標簽翻譯成java代碼,不過這次和jsp略有不同,它還要經過幾個過程。 先來看一下,實現一個tag的2個要點: 1. 提供屬性的set方法,此後這個屬性就可以在jsp頁面設置。以jstl標簽爲例 c:out value=""/,這個value就是jsp數據到tag之間的入口。所以tag裏面必須有一個setValue方法,具體的屬性可以不叫value。例如setValue(String data){this.data = data;}。這個“value”的名稱是在tld裏定義的。取什麽名字都可以,只需tag裏提供相應的set方法即可。 2. 處理 doStartTag 或 doEndTag 。這兩個方法是 TagSupport提供的。還是以c:out value=""/爲例,當jsp解析這個標簽的時候,在“<”處觸發 doStartTag 事件,在“>”時觸發 doEndTag 事件。通常在 doStartTag 裏進行邏輯操作,在 doEndTag 裏控制輸出。 在處理tag的時候: 0. 從tagPool中取得對應tag。 1. 爲該tag設置頁面上下文。 2. 爲該tag設置其父tag,如果沒有就爲null。 3. 調用setter方法傳入標簽屬性值tag,如果該標簽沒有屬性,此步跳過。 4. 調用doStartTag方法,取的返回值。 5. 如果該標簽有body,根據doStartTag返回值確定是否pop該標簽內容。如果要pop其body,則:setBodyContent(),在之後,doInitBody()。如果該標簽沒有body,此步跳過。 6. 調用doEndTag()以確定是否跳過頁面剩下部分。 7. 最後把tag類返還給tagPool。 tag類爲: package my.customtags; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.PageContext; import javax.servlet.jsp.tagext.TagSupport; public class Hidden extends TagSupport{ String name; public Hidden(){ name = ""; } public void setName(String name){ this.name = name; } public void release(){ value = null; } public int doStartTag(){ return EVAL_BODY_INCLUDE;} public int doEndTag() throws JspTagException{ try{ pageContext.getOut().write(", you are welcome"); } catch(IOException ex){ throw new JspTagException("Error!"); } return EVAL_PAGE; } } Jsp頁面: <my:hidden name="testname"/> 生成的jsp代碼: my.customtags.Hidden _jspx_th_my_hidden_11 = (my.customtags.Hidden) _jspx_tagPool_my_hidden_name.get(my.customtags.Hidden.class); _jspx_th_my_hidden_11.setPageContext(pageContext); _jspx_th_my_hidden_11.setParent(null); _jspx_th_my_hidden_11.setName("testname"); int _jspx_eval_my_hidden_11 = _jspx_th_my_hidden_11.doStartTag(); if (_jspx_th_my_hidden_11.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) return true; _jspx_tagPool_my_hidden_name.reuse(_jspx_th_my_hidden_11); return false; Taglib技術提供兩個機制,Body和non-Body導致了taglib的出現了兩個分支:Display Tag和Control Tag, 前者在java code中嵌入了html標簽,相當與一個web component,而後者則是另一種模板腳本。 四、兩種技術方案的比較: 1. 技術學習難易度 模板技術。使用模板技術,第一點就是必須學習模板語言,尤其是強控制的模板語言。于是模板語言本身的友好性變的尤爲重要。以下依據友好性,表現力以及複用性三點爲主基點比較了一下幾種模板技術。 Velocity: Turbine項目([url=http://jakarta.apache.org/%20Turbine]http://jakarta.apache.org/Turbine[/url])采用了velocity技術。 1. 友好性不夠。理由: 強控制類型,出現頁面顯示控制代碼和html混合。與Html的不兼容,無法所見即所得。遇到大的HTML頁面,從一個 “#if”找到對應的 “#end”也是很痛苦的一件事情。 2. 表現力強。理由:強控制語言。 3. 複用性弱。理由:模板腳本和頁面代碼混合。 XSLT Cocoon項目([url=http://cocoon.apache.org/]http://cocoon.apache.org/[/url])采用XML + XSLT的方法。CSDN社區也是采用此方案。 1. 內容和顯示風格分離,這點XSLT做的最好。 2. 速度慢。理由:XSLT的使用XPath,由于是要解析DOM樹,當XML文件大時,速度很慢。 3. 友好性不夠。理由:由于沒有HTML文件,根本看不到頁面結構、顯示風格和內容。XSL語法比較難以掌握,由于沒有“所見即所得”編輯工具,學習成本高。 4. 表現力強。理由:強控制語言。 5. 複用性弱。理由:xsl標簽和html標簽混合。 JDynamiTe 1. 表現力中等。理由:弱控制語言。 2. 友好性強。理由:所見即所得的效果。在模板件中的ignore block在編輯條件下可展示頁面效果,而在運行中不會被輸出。 3. 複用性強。理由:利用html標簽。 Tapestry 1. 友好性中等。理由:整個Tapestry頁面文件都是HTML元素。但是由于component會重寫html標簽,其顯示的樣子是否正確,將不預測。 2. 表現力強。理由:強控制語言。 3. 複用性強。理由:擴展了HTML元素的定義。 在JSP中大量的使用TagLib,能夠使得JSP的頁面結構良好,更符合XML格式,而且能夠重用一些頁面元素。但TagLib的編譯之後的代碼龐大而雜亂。TabLib很不靈活,能完成的事情很有限。TabLib代碼本身的可重用性受到TagSupport定義的限制,不是很好。 另外是,我不得不承認的一件事是,TagLib的編寫本身不是一件愉快的事情,事實我個人很反對這種開發方式。 2. 技術使用難易度 模板技術:模板技術本身脫離了Web環境,可以在不啓動Web server得情況下進行開發和測試,一旦出錯詳細的信息易于錯誤的定位。由于模板引擎的控制,頁面中將只處理顯示邏輯(盡管其可能很複雜) JSP技術:工作在Web環境下,開發測試一定要運行web server。此外,一些TagLib能夠産生新的標簽,頁面的最終布局也必須在web環境下才可以確定。測試時出錯信息不明確,特別是TagLib得存在,極不容易定位。由于其本質是程序,很容易在其中寫入業務邏輯,甚至于數據庫連接代碼,造成解耦的不徹底。 3. 總結 模板技術更加專注于頁面的顯示邏輯,有效幫助開發人員分離視圖和控制器。在學習,開發和測試都更加容易。 JSP技術本身是一個早期的技術,本身並沒有提出足夠的方式來分離視圖和控制器。相反,我認爲其本身是鼓勵開發人員不做解耦,因爲在JSP代碼中插入業務邏輯是如此的容易。
󰈣󰈤
 
 
 
>>返回首頁<<
 
 
 
 
 熱帖排行
 
王朝網路微信公眾號
微信掃碼關註本站公眾號 wangchaonetcn
 
  免責聲明:本文僅代表作者個人觀點,與王朝網絡無關。王朝網絡登載此文出於傳遞更多信息之目的,並不意味著贊同其觀點或證實其描述,其原創性以及文中陳述文字和內容未經本站證實,對本文以及其中全部或者部分內容、文字的真實性、完整性、及時性本站不作任何保證或承諾,請讀者僅作參考,並請自行核實相關內容。
 
© 2005- 王朝網路 版權所有