簡介
Jakarta Tapestry是一個開源的Java Web應用框架。你或許會說:"大同小異的東西罷了."多數
情況下,我同意你的觀點,然而,只要花上些許時間研究一下,你會發現Tapestry跟別的框架大為不同,它是值得嚴肅對待的。
Tapestry是一個基于控件的框架以致于用它開發Web應用類似開發傳統的GUI應用。你用Tapestry開發Web應用時你無需關注以操作為中心的(operation-centric) Servlet API.引用Tapestry網站上的一句話:"Tapestry用對象(objects),方法(methods),屬性(properties)替代以往的URLs和查詢參數, 重新詮釋Web應用開發.Tapestry 3.0即將發布,它有大量的改進和新的特性。
Tapestry的目標
簡單性
Tapestry應用與傳統的Servlet應用相比代碼量更少。大多數傳統的Servlet應用包含如下厭煩和
重復的任務:解析查詢參數,處理HttpSession對象,構建URLs。Tapestry消除了傳統Servelt應用中許多無趣的"銜接"代碼("plumbing" code)使開發者把精力集中到應用邏輯上來。
一致性
Tapestry為開發Web應用的頁面提供了一致的方式。這樣有助于消除傳統Servlet應用開發中臆測。由于所有Tapestry應用中的頁面都是用相同的可復用的控件組織而成,所以工作方式是相似的。
效率
Tapestr應用擁有高度的可升級性,它利用緩存和對象池使每個請求的處理時間最小化。Tapestry應用擁有跟傳統Servlet應用相仿的性能。
錯誤反饋
任何開發過Servlet/JSP應用的人毫無質疑有類似經歷:為了找出Web.xml文件有什么錯誤,不得不花費大量時間察看瀏覽器中的堆棧信息。Tapestry擁有優秀的錯誤報告方式,最值得一提的是它會指出哪個文件以及那一行導致了錯誤。
與Struts比較
既然Apache Struts可能是當今應用最廣泛的Web應用框架,拿Tapestry與它比較是唯一公平的。以下是一些觀察比較,它們來源于為這篇文章開發的幾個簡單的Tapestry應用和為幾個Struts工程的工作經歷。
Struts的優點
1 一個Servlet/JSP開發者熟悉Struts無需太久。然而Tapestry的學習曲線會長一點,因為它與流行的Web應用框架不太相同 。
2 Struts在Java社區里被廣泛接受和使用。為你的項目找一個好的Struts開發人員并非難事,Tapestry近來在開發者社區里 贏得一些關注,然而仍有許多Java老手不知道Tapestry為何物。
3 既然Struts被如此廣泛的使用,所以有很多Struts資源可供參考。相比大多數開源軟件,Tapestry擁有非常可觀的資源和文檔,但跟Struts相比仍有差距。
Tapestry的優點
1 你開發一個Tapestry應用無需關注Servlet API,你也無須為你的Servlet應用寫一些典型的"銜接"代碼。雖然Struts簡化了工作,然而Servlet API 仍是你需面對的。
2 Tapestry的頁面模板除了幾個特別屬性和標識就是一個標準的HTML文件。 如果你是一個開發小組中的HTML設計高手,然而你不懂Java或JSP,這就給你帶來很大方便。
3 因為Tapestry頁面是標準的HTML,所以可以用HTML所見即所得(WYSIWYG)編輯器編輯和預覽該頁。當一個頁需要修改它的外觀并不需要通知服務器讓它重新編譯JSP.
4 Tapestry不需要一個至高的,整個應用范圍的配置文件。Tapestry應用中的每一頁是獨立的,改變一頁不會影響開發其他 頁面的開發者,因為并沒有一個配置文件把所有頁面的瀏覽粘連在一起。
5 Tapestry擁有極好的錯誤報告。如果你在一個頁的模板或頁面規范犯了一個錯誤,Tapestry會指出導致錯誤的行號。
6 用Tapestry開發是種樂趣。這樣說聽上去似乎老調,然而用Tapestry開發一個Web應用相比應用其他流行的框架更為自然有趣。用Tapestry開發是應用了一種基于控件的架構,與開發傳統的GUI應用非常相似。
Tapestry 架構
Tapestry框架是標準Servlet API的一種擴展。它需要J2SDK1.2或更高版本的J2SDK和一個與Servlet API 2.2(或更高)兼容的應用服務器/Servlet容器。
一個Tapestry應用由許多擁有唯一名稱的頁面組成。一個頁面由一個模板和一些可復用的控件構成。模板由標準的HTML標簽和一些額外的屬性和標簽構成,這些額外的屬性和標簽是為了告訴Tapestry框架這個頁面的那些部分是由Tapestry控件組成。
簡單的Tapestry應用
為了最好的描述構建一個Tapestry頁面的方方面面,我們可以看看這個Pig Latin翻譯器應用的代碼。這個應用只有一個頁面,在這頁里輸入一個text值把它翻譯成Pig Latin,然后把翻譯好的值顯示給用戶。
在Tapestry應用中每個頁由3個部分組成:一個HTML模板,一個頁面規范文件,一個Java類。
這里有這個頁面屏幕抓圖:

頁面模板由標準的HTML標簽和一些額外的屬性和標簽構成,這些額外的屬性和標簽是為了告訴Tapestry框架這個頁面的那些部分是由Tapestry控件組成。頁面模板存放在Web應用的根context目錄下。通常,Tapestry在啟動時會尋找和呈現一個名叫"Home"的頁。雖然我們可以改變這種行為,但依照Tapestry的慣例會更簡單。
Home.html
??? <html>
??? <head>
????? <title>Tapestry Pig Latin Translator</title>
??? </head>
??? <body>
????? <h1>Pig Latin Translator</h1>
????? <form jwcid="@Form"① listener="ognl:listeners.submit"②>
????? <table border="1">
??????? <tr>
????????? <td>Value to Translate:</td>
????????? <td>
??????????? <input type="text" jwcid="@TextField"③ value="ognl:inputValue"/>
????????? </td>
??????? </tr>
??????? <tr>
????????? <td>Pig Latin:</td>
????????? <td>
??????????? <jwcid="@Insert"④ value="ognl:pigLatinValue"/>
????????? </td>
??????? </tr>
????? </table>
????? <input type="submit" jwcid="@Submit"⑤ value="Translate"/>
????? </form>
??? </body>
??? </html>
???
頁面模板的絕大部分是普通的HTML,只有少部分Tapestry特有的屬性和標簽。這種模板機制的優勢就是Tapestry頁面模板可以在一個可見即所得的編輯器里創建和預覽。描述Tapestry控件部分的標識是有限的和突出的。
標識里的jwcid所指是被應用的Tapestry控件的Java Web Component ID.在上面代碼斷里,我們是隱式地使用控件。隱式的控件是指直接在頁面模板里聲明使用的控件,。jwcid的前綴 '@ '符號就是通知Tapestry這里聲明使用了一個隱式控件。
在上面的Pig Latin Translator頁面模板里用了四個控件:Form①, TextField③, Insert④ 和Submit⑤。它們只是Tapestry框架提供的包含超過40個控件的控件庫里的四個。在后面的范例中,我們將會看到如何使用顯式控件。顯式控件是指控件在頁面規范文件里聲明后再使用的控件。
在前面的HTML模板里,使用控件的同時,也為控件指定了參數。例如控件Form①有一個listener②參數它指定了當表單提交時對應的頁面類調用的方法名稱。那個ognl:前綴的使用貫穿頁面的HTML模板,指向的是Object Graph Navigation Language (OGNL)。OGNL是一個強大的開源的表達式語言,用于將頁面內控件的屬性綁定到頁面類的屬性。
現在我們看看頁面規范文件。頁面規范文件是一個擴展名為page的XML文件,這個文件有許多職責,在眾多職責中最基本是指定頁面對應的Java類。頁面規范文件存放在webapp的WEB_INF目錄。
Home.page
??? <?xml version="1.0"?>
??? <!DOCTYPE page-specification PUBLIC
??????? "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
??????? "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd>
??? <page-specification class="Home">
??????? <property-specification name="inputValue" type="java.lang.String"/>
??????? <property-specification name="pigLatinValue" type="java.lang.String"/>
??? </page-specification>
???
頁面規范文件的根元素有一個class的屬性,它指定了這個頁對應的Java類。這個類必須要實現org.apache.tapestry.Ipage接口。頁面規范同時頁定義了兩個屬性(property)元素,以便Tapestry在頁面類里創建新的屬性。
Tapestry框架提供了org.apache.tapestry.html.BasePage class,它實現了Ipage接口。頁面類被存放在Web-INF/classes目錄下,跟你的Web應用的所需要的其他類放在一起。
Home.java
??? import org.apache.tapestry.html.BasePage;
??? import org.apache.tapestry.IRequestCycle;
??? public abstract class Home extends BasePage {
??????? public abstract String getInputValue();
??????? public abstract void setInputValue(String inputValue);
??????? public abstract String getPigLatinValue();
??????? public abstract void setPigLatinValue(String pigLatinValue);
??????? public void submit(IRequestCycle cycle) {
??????????? String inputValue = getInputValue();
??????????? String pigLatinValue = new PigLatinTranslator().translate(inputValue);
??????????? setPigLatinValue(pigLatinValue);
??????? }
??? }
???
你要提醒的第一件事或許是這個類為什么是抽象類。它還有幾個抽象方法訪問inputValue,pigLatinValue屬性。這里利用了Tapestry會在運行時刻創建子類的功能,這個子類會創建你在頁面規范里聲明的屬性和生成相應的訪問方法。
在表單提交時頁面類的submit方法會被調用。為什么會這樣?因為我們在頁面模板里將Form控件的listener屬性指定為:ognl:listeners.submit。這就意味著一個名叫submit的listener會通過頁面類的listeners被訪問。
所有的頁面類和控件類都從org.apache.tapestry.AbstractComponent這個類繼承來一個叫listeners的屬性。當submit方法完成后,頁面會顯示被翻譯好的詞。
最后講講Web.xml這個Web發布描述文件。Tapestry,像許多其他的流行的Web應用框架一樣,由一個Servlet構成,但是還需要一個發布描述文件。那個發布描述文件應該被存放在WEB-INF目錄。
web.xml
??? <?xml version="1.0"?>
??? <!DOCTYPE web-app PUBLIC
??????? "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
??????? "http://java.sun.com/dtd/web-app_2_3.dtd>
??? <web-app>
??????? <display-name>Tapestry Pig Latin Translator</display-name>
??????? <servlet>
??????????? <servlet-name>tapestry</servlet-name>
??????????? <servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class>
??????????? <load-on-startup>1</load-on-startup>
??????? </servlet>
??????? <servlet-mapping>
??????????? <servlet-name>tapestry</servlet-name>
??????????? <url-pattern>/app</url-pattern>
??????? </servlet-mapping>
??? </web-app>
???
雖然Pig Latin翻譯應用非常簡單,但是它會讓你對在Tapestry應用中一個頁面的3個組成部分有了基本的了解。它也展示了創建一個Tapestry應用的一個頁面只需寫多么少的代碼。
表單輸入驗證
Tapestry 提供了一些控件以便校驗用戶的輸入。校驗子系統是ValidField控件的核心。在下面的登錄應用中我們將使用ValidField控件。ValidField控件位于表單內,對用戶在客戶端的校驗提供了有用的反饋和視覺上的錯誤提示。
區域化
在Tapestry中,區域化是相當簡單的。Tapestry允許文字和圖形的區域化。為了區域化頁面的內容,你可以為每一個添加一個properties文件,或者提供一個區域化的模板。為每個頁面提供一個資源文件的方式遠比管理和維護一個巨大的全局的應用范圍的資源文件簡單。如果頁面的區域化并不僅僅只是文字信息的區域化,例如頁面的布局不同或者組成的控件不同,這樣情況提供區域化的頁面模板就能派上用場了。我們會在下面的登錄應用的使用Tapestry區域化。
創建控件
Tapestry發布時提供了40多個自帶的控件。如果你想知道更多的關于Tapestry自帶控件的信息,請參考Tapestry Component Reference.想看看Tapestry的控件的應用范例可訪問Tapestry Component Workbench.如果你發現你需要一個Tapestry本身沒提供的控件,你可以自己創建一個。創建你自己的Tapestry控件跟創建一個頁面是相似的。一個典型的Tapestry控件由一個控件規范文件(XML文檔),一個HTML控件模板,一個實現了org.apache.tapestry.Icomponent接口的Java類。這個議題有點超出本文的范圍,但是如果你有興趣學習如何創建你自己的Tapestry控件,你可以參考Tapestry的原創人,Tapestry In Action 一書的作者--Howard Lewis Ship寫的 Designing Tapestry Mega-Components 。
Tapestry 登錄應用
你在Pig latin翻譯器應用中看到了Tapestry的一些基本特性。與其用一個復雜的應用展示Tapestry所有的特性以致于壓得你揣不過氣來,還不如通過一些簡單的應用讓你找到一點對Tapestry的感覺。下面這個應用展示Tapestry如何處理頁面導航,區域化,驗證和其他一些特性。
這里有一個Home頁的屏幕抓圖,下面跟著它的頁面模板。

Home.html
??? <html>
??? <head>
??????? <title>Welcome to the Tapestry Login Application</title>
??? </head>
??? <body>
??????? <h1>Welcome to the Tapestry Login Application</h1>
??????? <span jwcid="@PageLink"① page="Login">Login</span>
??? </body>
??? </html>
???
這個Home頁的頁面模板除了一個jwcid屬性定義使用一個Tapestry PageLink①控件以外都是標準的HTML。
PageLink控件生成了一個指向Login頁的超鏈接。既然Home頁沒有任何動態的行為所以它不需要頁面規范和頁面對應的Java類。
這里是Login頁的屏幕抓圖,后面跟著是它的頁面模板。

Login.html
??? <html>
??? <head>
??????? <title>
??????????? <span key="title">①Login</span>
??????? </title>
??? </head>
??? <body jwcid="@Body">②
??????? <span jwcid="@Conditional" condition="ognl:beans.delegate.hasErrors">③
??????????? <div style="color: red">
??????????????? <span jwcid="@Delegator" delegate="ognl:beans.delegate.firstError">④
??????????????????? Error Message
??????????????? </span>
??????????? </div>
??????? </span>
??????? <p style="font-weight: bold" >
??????????? <span key="hint">Hint: Your password is your username spelled backwards.</span>
??????? </p>
???????
??????? <form jwcid="@Form" listener="ognl:listeners.login" delegate="ognl:beans.delegate">
⑤
??????????? <table>
??????????????? <tr>
??????????????????? <td align="right">
??????????????????????? <span jwcid="@FieldLabel" field="ognl:components.inputUsername"⑥>
??????????????????????????? Username:
??????????????????????? </span>
??????????????????? </td>
??????????????????? <td>
??????????????????????? <input type="text" jwcid="inputUsername"⑦ value="simpson_h"
size="30"/>
??????????????????? </td>
??????????????? </tr>
??????????????? <tr>
??????????????????? <td align="right">
??????????????????????? <span jwcid="@FieldLabel" field="ognl:components.inputPassword">
??????????????????????????? Password:
??????????????????????? </span>
??????????????????? </td>
??????????????????? <td>
??????????????????????? <input type="text" jwcid="inputPassword" hidden="true" value=""
size="30"/>
??????????????????? </td>
??????????????? </tr>
??????????????? <tr>
??????????????????? <td colspan="2" align="center">
??????????????????????? <input type="submit" jwcid="@Submit" value="message:login"/>
??????????????????? </td>
??????????????? </tr>
??????????? </table>
??????? </form>
??? </body>
??? </html>
???
這個頁面模板大多數是通常的HTML。我們從頁面模板中可以看到Tapestry的區域化特性:它使用一個span元素,這個span元素帶有一個叫key的屬性,key的值映射到Login.properties文件里一個屬性。一個Body控件被聲明使用,因為它對客戶端的JavaScript校驗是必需的。
為Form component⑤設定delegate屬性激活表單輸入驗證。delegate屬性是我們在頁面規范里聲明的org.apache.tapestry.valid.IvalidationDelegate的實現類。如果驗證錯誤發生了,我們用Conditional component③控件判斷delegate是否有任何錯誤,如果有就把第一個錯誤④顯示給用戶。如果ognl 表達式ognl:beans.delegate.hasErrors 為true,Conditional控件將顯示它的內容實體。所有的頁面類和控件類都從AbstractComponent繼承來一個叫beans的屬性。這個beans屬性是一個org.apache.tapestry.IbeanProvider的實例,利用它可以通過名字取得在頁面規范文件里定義的beans.FieldLabel⑥被用于為inputuserName validField控件顯示標簽,這個FieldLabel控件也被用來與表單的驗證代理協作,指出包含錯誤的輸入域。
InputUserName⑦控件是一個顯示控件的例子。顯式控件是指在頁面規范文件聲明的控件。InputUsername和inputPassword控件都是顯式的,它們與FieldLabel聯合顯示它們的displayName屬性。
下面的是Login頁的資源文件。Login.properties跟頁面規范一并存放在WEB-INF目錄。
Login.properties
??? title = Login to the Application
??? hint = Hint: Your password is your username spelled backwards.
??? login = Login
??? username = Username:
??? password = Password:
??? invalidpassword = Invalid Password
???
Here is the page specification for the Login page.
Login.page
??? <?xml version="1.0"?>
??? <!DOCTYPE page-specification PUBLIC
??????? "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
??????? "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd>
??? <page-specification class="com.ociweb.tapestry.Login">
??????? <bean name="delegate" class="org.apache.tapestry.valid.ValidationDelegate"/>①
??????? <bean name="requiredValidator"②
??????????? class="org.apache.tapestry.valid.StringValidator">
??????????? <set-property name="required" expression="true"/>
??????????? <set-property name="clientScriptingEnabled" expression="true"/>
??????? </bean>
??????? <property-specification name="username" type="java.lang.String"/>
??????? <property-specification name="password" type="java.lang.String"/>
??????? <component id="inputUsername" type="ValidField"> ③
??????????? <message-binding name="displayName" key="username"/> ④
??????????? <binding name="validator" expression="beans.requiredValidator"/> ⑤
??????????? <binding name="value" expression="username"/> ⑥
??????? </component>
??????? <component id="inputPassword" type="ValidField"> ⑦
??????????? <message-binding name="displayName" key="password"/>
??????????? <binding name="validator" expression="beans.requiredValidator"/>
??????????? <binding name="value" expression="password"/>
??????? </component>
??? </page-specification>
???
Page-specification元素的class屬性和兩個property-specification元素與Pig Latin翻譯器應用是相似的。
你會發現第一個新東西-bean元素①,bean元素把一個org.apache.tapestry.valid.ValidationDelegate
的實例指定了名稱"delegate"。頁面HTML模板里的Form控件把它的參數delegate設定為
ognl:beans.delegate,就是指向了這個org.apache.tapestry.valid.ValidationDelegate實例。
Bean元素②把一個org.apache.tapestry.valid.StringValidator的實例指定了名稱"
requiredValidator",以用于驗證。這個bean的required屬性被設為true表明使用這個bean的域是必須被驗證的。這個bean的clientScriptingEnabled屬性被設定為ture,表明使用這個bean的域客戶端的JavaScript驗證功能是激活的。RequiredValidator bean被用于驗證inputUsername和inputPassword的內容。
控件inputUsername③被控件規范聲明為ValidField,ValidField是一種用于Tapestry驗證子系統的特殊版本的TextField控件。Message-binding元素被用于指定inputUsername控件的displayName參數的值,這個值是用"username"為關鍵字從login.properties④文件里得到。InputUsername控件的validator參數被設定為requiredValidator bean,這是我們在頁面規范里聲明過的⑤。控件的value參數跟頁面Java類的username屬性綁定在一起⑥??丶nputPassword的控件規范跟控件inputUsername幾乎相似,除了用于取得displayName的關鍵字和綁定的頁面Java類的屬性不同。
通過使用ValidField控件和為表單(form)提供一個ValidationDelegate, 我們激活了Login表單的驗證功能。除了服務器端的驗證,Tapestry也提供了客戶端的驗證(利用JavaScript)。下面就是當用戶提交一個表單而沒有為UserName域提供值時,一個JavaScript錯誤對話框彈出時的屏幕抓圖。

下面就是當用戶提交一個表單而沒有為Password域提供值時,一個JavaScript錯誤對話框彈出時的屏幕抓圖。

下面就是Login頁對應的Java 類。
Login.java
??? package com.ociweb.tapestry;
??? import org.apache.tapestry.html.BasePage;
??? import org.apache.tapestry.IRequestCycle;
??? import org.apache.tapestry.valid.ValidationConstraint;
??? import org.apache.tapestry.valid.IValidationDelegate;
??? public abstract class Login extends BasePage {
??????? public abstract String getUsername();
??????? public abstract void setUsername(String username);
??????? public abstract String getPassword();
??????? public abstract void setPassword(String password);
??????? public void login(IRequestCycle cycle) {
??????????? String username = getUsername();
??????????? String password = getPassword();
??????????? StringBuffer sb = new StringBuffer(username);
??????????? String validPassword = sb.reverse().toString();
??????????? if (password.equals(validPassword)) {
??????????????? cycle.activate("Success");①
??????????? } else {
??????????????? String errorMessage = getMessage("invalidpassword");②
??????????????? IValidationDelegate validationDelegate =
??????????????????????? (IValidationDelegate) getBeans().getBean("delegate");③
??????????????? validationDelegate.record(errorMessage,
????????????????????????????????????????? ValidationConstraint.CONSISTENCY);④
??????????? }
??????? }
??? }
???
跟Pig Latin翻譯器應用一樣,我們的頁面類也是抽象的,它有抽象方法訪問在頁面規范里定義的屬性(properties)。Tapestry會在運行時刻創建username和password屬性。Login方法只是簡單的驗證一下用戶輸入的密碼值是否剛好是用戶名的反向。如果密碼通過驗證,用戶將被引領導Success page①。
如果密碼輸入有誤,我們用關鍵字"invalidPassword"通過從org.apache.tapestry.AbstractComponent里繼承來的getMessage()方法從Login.properties②里查找對應的資源。我們需要把密碼錯誤信息紀錄到我們在頁面規范中定義的頁面validation delegate中去。我們可以利用我們在頁面規范中指定的名稱,從頁面的beans屬性中找回validationDelegate③。最后,我們調用org.apache.tapestry.valid.IvalidationDelegate的record方法把將要顯示給用戶看的錯誤信息保存起來。下面就是當用戶輸入錯誤密碼的提交后的屏幕抓圖。

下面就是Success頁的頁面模板。Success頁的頁面模板僅僅包含HTML標識,所以它不需要頁面規范和頁面
Java類。
Success.html
??? <html>
??? <head>
??????? <title>Successful Login</title>
??? </head>
??? <body>
??????? <p>
??????????? Congratulations! You have successfully logged on.
??????? </p>
??? </body>
??? </html>
???
總結
我希望這篇文章已經向你展示了Tapestry框架在構建Web應用的是多么簡單,然而優雅。Tapestry與大多數主流Web應用框架最大不同在于它讓你用基于控件的方式開發,而非以操作為中心的方式開發。如果這篇文章激起了你的興趣,我建議你把它下載下來利用它你自己的簡單的Web應用。通過感受簡單的應用,這是你了解這個框架的優點的唯一途徑。如果你想在你的下一個項目里使用Tapestry,我強烈建議你購買
Tapestry In Action 這本書。我擁有這本書,對它我感到很滿意。
References
1 Zip file with all source code and war files from the article. (12K)
???????????????? http://www.ociweb.com/jnb/jnb2004_05.zip
2 Tapestry Home Page http://jakarta.apache.org/tapestry/
3 Tapestry In Action Page http://www.manning.com/lewisship/
4 Tapestry Wiki http://jakarta.apache.org/tapestry/wiki_frame.html
5 OGNL page http://www.ognl.org/
6 Tapestry Component Reference
???????????????? http://jakarta.apache.org/tapestry/doc/ComponentReference/index.html
7 Tapestry Component Workbench http://www.t-deli.com/app
8 Designing Tapestry Mega-Components
???????????????? http://www.onjava.com/pub/a/onjava/2001/11/21/tapestry.html
注:原文地址:http://www.inspiresky.com/Article/java/2006-02-14/412.html