(從csdn的blog上同步過來)
(本文發(fā)于java emag第一期)
關于 Template 和 JSP 的起源還要追述到 Web 開發(fā)的遠古年代,那個時候的人們用 CGI 來開發(fā) web 應用,在一個 CGI 程序中寫 HTML 標簽。
在這之后世界開始朝不同的方向發(fā)展: sun 公司提供了類似于 CGI 的 servlet 解決方案,但是無論是 CGI 還是 servlet 都面對同一個問題:在程序里寫 html 標簽,無論如何都不是一個明智的解決方案。于是 sun 公司 于1999年推出了JSP技術。 而在另一個世界里,以 PHP 和 ASP 為代表的 scriptlet 頁面腳本技術開始廣泛應用。
不過即便如此,問題并沒有結束,新的問題出現(xiàn)了:業(yè)務和 HTML 標簽的混合,這個問題不僅導致頁面結構的混亂,同時也使代碼本身難以維護。
于是來自起源于 70 年代后期的 MVC 模式被引入開發(fā)。 MVC 的三個角色: Model ——包含除 UI 的數(shù)據(jù)和行為的所有數(shù)據(jù)和行為。 View 是表示 UI 中模型的顯示。任何信息的變化都由 MVC 中的第三個成員來處理——控制器。
在之后的應用中,出現(xiàn)了技術的第一次飛躍:前端的顯示邏輯和后端的業(yè)務邏輯分離, COM 組件或 EJB 或 CORBA 用于處理業(yè)務邏輯, ASP 、 JSP 以及 PHP 被用于前端的顯示。這個就是 Web 開發(fā)的 Model 1 階段(頁面控制器模式)。
不過這個開發(fā)模式有很多問題:
1.?????? 頁面中必須寫入 Scriptlet 調用組件以獲得所必需的數(shù)據(jù)。
2.?????? 處理顯示邏輯上 Scriptlet 代碼和 HTML 代碼混合交錯。
3.?????? 調試困難。 JSP 被編譯成 servlet ,頁面上的調試信息不足以定位錯誤。
這一切都是因為在 Model 1 中并沒有分離視圖和控制器。完全分離視圖和控制器就成了必須。這就是 Model 2 。它把 Model 1 中未解決的問題——分離對組件(業(yè)務邏輯)的調用工作,把這部分工作移植到了控制器。現(xiàn)在似乎完美了,不過等等,原來的控制器從頁面中分離后,頁面所需的數(shù)據(jù)怎么獲得,誰來處理頁面顯示邏輯?兩個辦法: 1. 繼續(xù)利用 asp , php 或者 jsp 等機制,不過由于它們是運行在 web 環(huán)境下的,他們所要顯示的數(shù)據(jù)(后端邏輯產生的結果)就需要通過控制器放入 request 流中; 2. 使用新手法——模板技術,使用獨立的模板技術由于脫離的了 web 環(huán)境,會給開發(fā)測試帶來相當?shù)谋憷V劣陧撁嫠钄?shù)據(jù)傳入一個 POJO 就行而不是 request 對象。
模板技術最先開始于
PHP
的世界,出現(xiàn)了
PHPLIB Template
和FastTemplate這兩位英雄。不久模板技術就被引入到java web開發(fā)世界里。目前比較流行的模板技術有:XSTL,Velocity,
JDynamiTe
,Tapestry等。另外因為JSP技術畢竟是目前標準,相當?shù)南到y(tǒng)還是利用JSP來完成頁面顯示邏輯部分,在Sun公司的JSTL外,各個第三方組織也紛紛推出了自己的Taglib,一個代表是struts tablib。
模板技術從本質上來講,它是一個占位符動態(tài)替換技術。一個完整的模板技術需要四個元素: 0. 模板語言, 1. 包含模板語言的模板文件, 2. 擁有動態(tài)數(shù)據(jù)的數(shù)據(jù)對象, 3. 模板引擎。以下就具體討論這四個元素。(在討論過程中,我只列舉了幾個不同特點技術,其它技術或有雷同就不重復了)
模板語言包括:變量標識和表達式語句。根據(jù)表達式的控制力不同,可以分為強控制力模板語言和弱控制力模板語言。而根據(jù)模板語言與 HTML 的兼容性不同,又可以分為兼容性模板語言和非兼容性模板語言。
模板語言要處理三個要點:
1. 標量標記。把變量標識插入 html 的方法很多。其中一種是使用類似 html 的標簽;另一種是使用特殊標識,如 Velocity 或者 JDynamiTe ;第三種是擴展 html 標簽,如 tapestry 。采用何種方式有著很多考慮,一個比較常見的考慮是“所見即所得”的要求。
2. 條件控制。這是一個很棘手的問題。一個簡單的例子是某物流陪送系統(tǒng)中,物品數(shù)低于一定值的要高亮顯示。不過對于一個具體復雜顯示邏輯的情況,條件控制似乎不可避免。當你把類似于 <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 機制,可以根據(jù) html 類型甚至是 element 的 attributes 來 apply 不同的樣式。
3. 迭代(循環(huán))。在網頁上顯示一個數(shù)據(jù)表單是一個很基本的要求,使用集合標簽將不可避免,不過幸運的是,它通常很簡單,而且夠用。特別值得一提的是 PHP 的模板技術和 JDynamiTe 技術利用 html 的注釋標簽很簡單的實現(xiàn)了它,又保持了“所見既所得”的特性。
下面是一些技術的比較:
Velocity |
變量定義:用 $ 標志 表達式語句:以 # 開始 強控制語言:變量賦值: #set $this = "Velocity" ??????????? 外部引用: #include ( $1 ) ??????????? 條件控制: #if …. #end 非兼容語言 |
JDynamiTe 變量定義:用 {} 包裝 表達式語句:寫在注釋格式( <!--? à )中 弱控制語言 兼容語言 |
XSLT 變量定義: xml 標簽 表達式: xsl 標簽 強控制語言:外部引用: import , include ??????????? 條件控制: if , ? choose…when…otherwise 非兼容語言 |
Tapestry 采用 component 的形式開發(fā)。 變量定義(組件定義):在 html 標簽中加上 jwcid 表達式語句: ognl 規(guī)范 兼容語言 |
模板文件指包含了模板語言的文本文件。
模板文件由于其模板語言的兼容性導致不同結果。與 HTML 兼容性的模板文件只是一個資源文件,其具有良好的復用性和維護性。例如 JDynamiTe 的模板文件不但可以在不同的項目中復用,甚至可以和 PHP 程序的模板文件互用。而如 velocity 的非兼容模板文件,由于其事實上是一個腳本程序,復用性和可維護性大大降低。
模板文件包含的是靜態(tài)內容,那么其所需的動態(tài)數(shù)據(jù)就需要另外提供。根據(jù)提供數(shù)據(jù)方式的不同可以分為 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", " |
2.?????? DOM :直接操作 DOM 數(shù)據(jù)對象,如 XSLT 利用 XPath 技術。
3.?????? POJO :直接利用反射取得 DTO 對象,利用 JavaBean 機制取得數(shù)據(jù)。如 Tapestry 。
模板引擎的工作分為三步:
1. 取得模板文件并確認其中的模板語言符合規(guī)范。
比如 velocity ,確定 #if 有對應得 #end 等。 Xml + xslt 的模型中, xml 文件標簽是否完整等。在完成這些工作后,模板引擎通常會把模板文件解析成一顆節(jié)點樹(包含模板文件的靜態(tài)內容節(jié)點和模板引擎所定義的特殊節(jié)點)。
2. 取得數(shù)據(jù)對象。
?????? ? 該數(shù)據(jù)對象一般通過程序傳遞引用實現(xiàn)。現(xiàn)有的大量框架在程序底層完成,處理方式也各自不同,有兩種技術分別為推技術和拉技術。推技術: controller 調用 set 方法把動態(tài)數(shù)據(jù)注入,模板引擎通過 get 方法獲得,典型代表: Struts ;拉技術:模板引擎根據(jù)配置信息,找到與 view 對應的 model ,調用 model 的 get 方法取得數(shù)據(jù),典型代表: Tapestry 。
3. 合并模板文件(靜態(tài)內容)和數(shù)據(jù)對象(動態(tài)內容),并生成最終頁面。
?????? ? 合并的機制一般如下,模板引擎遍歷這顆節(jié)點樹的每一個節(jié)點,并 render 該節(jié)點,遇到靜態(tài)內容節(jié)點按正常輸入,遇到特殊節(jié)點就從數(shù)據(jù)對象中去得對應值,并執(zhí)行其表達式語句(如果有的話)。
以下詳細說明:
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 方法執(zhí)行 http 請求。 每個 service 通過 RequestCycle 對象的 getPage 方法取得 Page 對象,并將其設置為該 Cycle 對象的 active Page 。之后 service 調用 renderResponse 方法執(zhí)行輸出。 renderResponse 調用 page 的 getResponseWriter(output) 取得 writer 對象,并把它傳給 cycle.renderPage(writer) 方法,該方法調用 page 的 renderPage 方法。 Page 執(zhí)行 renderPage 時,首先判斷是否有 listener 的請求,如果有則處理 listener 請求;然后調用 BaseComponentTemplateLoader 的 process 方法把模板文件載入并形成一個 component 節(jié)點樹,依次執(zhí)行節(jié)點的 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 ,一個偽裝后的 servlet 。 web server 會對任何一個 jsp 都生成一個對應 jsp 類,打開這個類,就會發(fā)現(xiàn), 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); ??? } ? } } |
??????
技術:
Taglib 作為 jsp 之上的輔助技術,其工作本質依托與 jsp 技術,也是自定義標簽翻譯成 java 代碼,不過這次和 jsp 略有不同,它還要經過幾個過程。
先來看一下,實現(xiàn)一個 tag 的 2 個要點:
1.
提供屬性的set方法,此后這個屬性就可以在jsp頁面設置。以jstl標簽為例 c:out value=""/,這個value就是jsp數(shù)據(jù)到tag之間的入口。所以tag里面必須有一個setValue方法,具體的屬性可以不叫value。例如setValue(String data){this.data = data;}。這個“value”的名稱是在tld里定義的。取什么名字都可以,只需tag里提供相應的set方法即可。
2.
處理 doStartTag 或 doEndTag 。這兩個方法是 TagSupport提供的。還是以c:out value=""/為例,當jsp解析這個標簽的時候,在“<”處觸發(fā) doStartTag 事件,在“>”時觸發(fā) doEndTag 事件。通常在 doStartTag 里進行邏輯操作,在 doEndTag 里控制輸出。
?
在處理tag的時候:
? 0.
從tagPool中取得對應tag。
1.????
為該tag設置頁面上下文。
2.????
為該tag設置其父tag,如果沒有就為null。
3.????
調用setter方法傳入標簽屬性值tag,如果該標簽沒有屬性,此步跳過。
4.????
調用doStartTag方法,取的返回值。
5.????
如果該標簽有body,根據(jù)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的出現(xiàn)了兩個分支:Display Tag和Control Tag, 前者在java code中嵌入了html標簽,相當與一個web component,而后者則是另一種模板腳本。
?????? 1. 技術學習難易度
模板技術。使用模板技術,第一點就是必須學習模板語言,尤其是強控制的模板語言。于是模板語言本身的友好性變的尤為重要。以下依據(jù)友好性,表現(xiàn)力以及復用性三點為主基點比較了一下幾種模板技術。
Velocity : Turbine 項目( http://jakarta.apache.org/Turbine )采用了 velocity 技術。 1.?????? 友好性不夠。理由: 強控制類型,出現(xiàn)頁面顯示控制代碼和 html 混合。與 Html 的不兼容,無法所見即所得。遇到大的 HTML 頁面,從一個 “ #if ”找到對應的 “ #end ”也是很痛苦的一件事情。 2.?????? 表現(xiàn)力強。理由:強控制語言。 3.?????? 復用性弱。理由:模板腳本和頁面代碼混合。 |
XSLT Cocoon 項目( http://cocoon.apache.org/ )采用 XML + XSLT 的方法。 CSDN 社區(qū)也是采用此方案。 1.?????? 內容和顯示風格分離,這點 XSLT 做的最好。 2.?????? 速度慢。理由: XSLT 的使用 XPath ,由于是要解析 DOM 樹,當 XML 文件大時,速度很慢。 3.?????? 友好性不夠。理由:由于沒有 HTML 文件,根本看不到頁面結構、顯示風格和內容。 XSL 語法比較難以掌握,由于沒有“所見即所得”編輯工具,學習成本高。 4.?????? 表現(xiàn)力強。理由:強控制語言。 5.?????? 復用性弱。理由: xsl 標簽和 html 標簽混合。 |
JDynamiTe 1.?????? 表現(xiàn)力中等。理由:弱控制語言。 2.?????? 友好性強。理由:所見即所得的效果。在模板件中的 ignore block 在編輯條件下可展示頁面效果,而在運行中不會被輸出。 3.?????? 復用性強。理由:利用 html 標簽。 |
Tapestry 1.?????? 友好性中等。理由:整個 Tapestry 頁面文件都是 HTML 元素。但是由于 component 會重寫 html 標簽,其顯示的樣子是否正確,將不預測。 2.?????? 表現(xiàn)力強。理由:強控制語言。 3.?????? 復用性強。理由:擴展了 HTML 元素的定義。
|
在 JSP 中大量的使用 TagLib ,能夠使得 JSP 的頁面結構良好,更符合 XML 格式,而且能夠重用一些頁面元素。但 TagLib 的編譯之后的代碼龐大而雜亂。 TabLib 很不靈活,能完成的事情很有限。 TabLib 代碼本身的可重用性受到 TagSupport 定義的限制,不是很好。 另外是,我不得不承認的一件事是, TagLib 的編寫本身不是一件愉快的事情,事實我個人很反對這種開發(fā)方式。
?????? 2. 技術使用難易度
?????? 模板技術:模板技術本身脫離了 Web 環(huán)境,可以在不啟動 Web server 得情況下進行開發(fā)和測試,一旦出錯詳細的信息易于錯誤的定位。由于模板引擎的控制,頁面中將只處理顯示邏輯(盡管其可能很復雜)
?????? JSP 技術:工作在 Web 環(huán)境下,開發(fā)測試一定要運行 web server 。此外,一些 TagLib 能夠產生新的標簽,頁面的最終布局也必須在 web 環(huán)境下才可以確定。測試時出錯信息不明確,特別是 TagLib 得存在,極不容易定位。由于其本質是程序,很容易在其中寫入業(yè)務邏輯,甚至于數(shù)據(jù)庫連接代碼,造成解耦的不徹底。
3. 總結
模板技術更加專注于頁面的顯示邏輯,有效幫助開發(fā)人員分離視圖和控制器。在學習,開發(fā)和測試都更加容易。
JSP 技術本身是一個早期的技術,本身并沒有提出足夠的方式來分離視圖和控制器。相反,我認為其本身是鼓勵開發(fā)人員不做解耦,因為在 JSP 代碼中插入業(yè)務邏輯是如此的容易。