程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> WEB開發模式Tapestry簡介

WEB開發模式Tapestry簡介

編輯:關於JAVA

前言

由於internet技術的廣泛流行,越來越多的程序開發模式正在從C/S向B/S轉換,由於B/S模式具有瘦客戶端的優點——無須安裝客戶端程序,這就大大減輕了軟件升級的費用,所有的業務邏輯和數據庫存儲都放在服務端,從而提高了系統的可維護性。而且由於HTTP協議是一個開放式的標准,因此只要是支持HTTP協議的浏覽器都可以運行,再結合目前的XML技術,可以方便地實現跨平台的分布式應用。

但B/S架構有其天生的缺點:無狀態性。這其實並不是B/S程序的錯,而是由於B/S程序是建立在http協議的基礎上的,因此程序無法維護各個客戶端的狀態,不過所幸的是這個問題現在已經解決了。

現行的B/S開發的另一大缺陷是:代碼和HTML頁面揉和在一起了,對程序員和美工非常不利,而且對代碼的維護簡直是一場惡夢,這相信是許多Web開發者的一種體會。而C/S結構就沒有這種缺陷,因為它是基於組件的,比如VB,你有多得數不清的ActiveX控件來完成一些復雜的界面。

也許你看到這裡,心裡在盤算著:還是用C/S算了,省得這麼多麻煩,但是一個不敢於嘗試新技術的公司往往是一個被新技術淘汰的公司。

為了解決上述問題,apache組織開發了一個基於JSP的MVC模式的實現:struts。它將WEB表現層分為model-view-controller幾個部分,通過一個servlet來對web的流程進行控件,這與單純的JSP或ASP相比確實進步了不少,因為利用struts能清楚地界定web的流程,而且不提倡使用<% …. %>語句,轉而代之的是taglib技術。但它始終沒有擺脫代碼和HTML頁面揉和的問題,而且它的原理對於JSP初學者來說比較復雜,taglib庫不夠豐富,而且taglib也不像組件那樣能夠繼承。

於是,新一代的WEB開發方法應運而生,它們最典型的就是開發方法是基於組件的,其中最具代表性的就是Tapestry。

為什麼要用Tapestry?

Tapestry現在已經屬於Apache的Jakarta項目下了,你可以訪問http://jakarta.apache.org/tapestry 來訪問更多的信息。

Tapestry是一個強有力、開放源碼、基於Java的用於開發高端WEB應用的framework。它是JSP的一種替代方法,使用Tapestry,可以讓你的頁面看不到一句JSP代碼,這使頁面看起來非常干淨。它可以用很少的代碼來構造一個極端復雜的WEB應用。

Tapestry使用了類似於傳統C/S的開發方法:基於組件的開發。使用tapestry,你就可以得到以下好處:

1、非常高的代碼復用性,因為在tapestry中,任何事物都可以看作一個可復用的組件。

2、將JSP開發者從繁瑣的JSP代碼中解脫出來,取而代之的是真正面像對像方法,而不是URL解析。

3、對頁面國際化的充分支持

4、精確地錯誤報告,可以將錯誤定位到源程序中的行,取代了JSP中那些莫名奇妙地錯誤提示。

充分支持團隊開發,美工人員和JAVA開發人員可以融洽地相處,互相都不依賴於對方。

你也許會問:如果tapestry是基於組件的,那麼它的組件是怎樣構成的呢?它是由一個定義文件(以XML的格式)、一個HTML模板、一個JAVA類。Tapestry的組件可以組合在一起形成一個更大的組件或邏輯頁面。

Tapestry的工作原理如下:在web.xml文件中定義一個名為ApplicationServlet的servlet來進行處理所有的http請求,這和struts有點相似,不過它主要職責是負責引導整個tapestry核心,啟動日志功能,讀取配置文件,創建工作引擎來指派客戶請求。ApplicationServlet主要是通過它的配置文件來進行自身管理的,你可以通過在web.xml中定義一個名為org.apache.tapestry.specification-path的初始參數來指定這個配置文件,當然如果你覺得這樣麻煩,那你也可以不指定配置文件,但tapestry會自動尋找與此servlet同名的配置文件,例如在web.xml中的定義如下:

<servlet>
<servlet-name>Registration</servlet-name>
<servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>

那麼Tapestry會自動尋找一個名為Registration.application的配置文件,如果用戶沒有定義此文件,那麼Tapestry將不能正常啟動。

為了便於理解,我舉一個大家都熟知的例子:用戶注冊模塊。首先生成一個注冊頁面,等待用戶的輸入,然後進行合法性檢查,如果通過,就顯示下一個頁面。如下所示

也許你會說,咦,這不是和JSP的表單差不多的嗎?是的,雖然看上去差不多,但原理大不一樣,由於它是基於組件的,所以Name、DOB、Address、City、State、Zip和Register按鈕都是單獨的組件,而不是普通的表單了。

這時你肯定會說:唉,也不過如此嘛,就算是基於組件的思想設計出來的東西也不過爾爾,這幾個文本框這麼簡單,何必費這麼大的勁把它們包裝成組件呢?呵呵,不知你注意到DOB這個文本框沒有?它和其它幾個有些不一樣,它的右邊多了一個小按鈕,如果點擊此按鈕,出現的結果會讓你瞠目結舌!

酷吧?如果你曾經是一個JSP程序員,你的腦海裡馬上會閃過這樣一個念頭:一定是用javascript做的! 如果你這樣想的話,那你就大錯特錯了,因為本人的javascript還沒達到這種水平,如果我告訴你完成這樣一個組件,只需要在配置文件中加入通俗易懂的幾個xml元素的時候,你是否會覺得這真的很神奇呢?

是的,這就是Tapestry的魅力所在,Tapestry主要設計者Howard Lewis Ship的目的就是最大限度地解放JSP程序員的勞動量,讓他們有更多的時間花在客戶的需求和程序的邏輯設計上,而不是花在和美工人員互相解決矛盾沖突上,你可能會急不可待的問:那我們是否可以動手試一試了呢?是的,當然可以,但在動手之前,我要先介紹一些關於tapestry的概念,並試著開發一個最簡單的“hello,world”程序來讓你熟悉一下它的配置。因為它是一個framework,如果你對它的結構不是很清楚的話,我想你肯定無法充分地利用它的。

一個Tapestry組件一般稱作JWC(Java Web Component),它能與其它JWC一起組合,生成實例,進行配置。比如你剛才看到的那個不可思議的組件就是一個DatePicker組件,剛才的那個頁面是一個Page組件,Tapestry自帶了很多的JWC組件,這些組件不僅僅只是GUI組件,還有可能是帶控制功能的組件,比如foreach組件能夠提供循環的功能,同時tapestry還提供了一個簡單的方法來制作用戶自定義的組件,所有的Tapestry應用的表示層都是由這些JWC構成的。

一個簡單的Hello,world程序

為了清楚地弄清tapestry的framwork構架,我們從最簡單的”hello,world”開如,打開組件的暗箱來看一看JWC的內部結構。一個典型的JWC包括3個部分----一個HTML模板,一個XML格式的定義文件和一個或多個JAVA類,盡管一個簡單的JWC可以僅僅是一個HTML模板,但在本例中我們還是使用這三個部分來展示它的內部結構。

先讓我們看一下演示的結果:

由於我們知道Page也是一種JWC組件,因此讓我們看一下這個Page組件是怎樣構成的,首先,我們看一下它的HTML模板結構:

Home.html

<html>
<head>
<title>Welcome to Tapestry!</title>
</head>
<body>
Hello <span jwcid="user">User Name</span>! Welcome to Tapestry!
</body>
</html>

這裡你可能看上去覺得非常眼熟,這不就是一個普通的HTML網頁嗎?且慢,如果你仔細觀察,會發現span標簽多了一個新的屬性jwcid,這正是tapestry設計的精妙之處,它的表現層可以說幾乎是99%的純HTML,只是增加了一個jwcid屬性,jwcid=”user”在這裡的意思是在<span> … </span>之間放置一個名為user的組件。這裡要指出的是jwcid屬性不僅僅可以放在span標簽內,它可以放在任何的HTML標簽內,比如<title>、<body>、<form>等等,至於為什麼要選<span>的原因是<span>標簽不影響網頁的輸出效果,這就大大方便了美工人員,他們可以直接使用frontpage或dreamwaver來進行設計,而不像JSP那樣無法在frontpage或dreamwaver中正常顯示。

也許你會問,網頁上輸出的用戶名明明是Jack嘛,為什麼HTML模板中是User name呢?這是因為當tapestry一旦發現某個HTML標簽有jwcid的屬性後,它就會把這個標簽當做一個組件來看待,至於標簽裡面的東西到底是原樣輸出或忽略就要依照那個組件的類型而定了,在這裡我們的user組件實際上是一種Insert類型的組件,因此會把<span>標簽內的內容忽略掉,其實也可以直接寫成<span jwcid=”user”></span>,效果也是一樣的,只不過加了User name後就方便了美工人員的排版設計。

請注意,HTML模板有它自己的命名規范,它的文件名應該與Page組件的定義文件相同,只不過是擴展名為.html,與普通的web服務器一般都有一個index.html類似,tapestry也有自己的”index.html”,只不過它的名字不是”index.html”,而是Home.html,那麼這些模板文件究竟應該放在哪兒呢?很簡單,放在當前應用的目錄下面就行了,比如我們的應用是Welcome,那把它放在webapps/Welcome目錄下面就行了。

Home.page

<?xml version="1.0" encoding="UTF-8"?>
<!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="demo.Home">
<component id="user" type="Insert">
<binding name="value" expression="userName"/>
</component>
<context-asset name="$template" path="Home.html"/>
</page-specification>

以上這個文件便是這個名為”Home”的Page組件的頁面定義文件,它好像是一座橋,聯系著HTML模板和JAVA類進行協同工作。這個定義文件“Home.page”實際上就是一個xml文件,它的根元素是page-specification,它有一個名為class的屬性,用來指示HTML模板與哪個java類協同工作,在本例中為demo.Home這個類。

Component元素是對HTML模板中引用的組件的定義,id屬性名一定要與HTML模板中的jwcid一樣,type指定了這個組件屬於哪種類型,在本例中為Insert組件,Insert組件是Tapestry中的一個基本組件,它的作用是在HTML模板中插入指定的文本,待插入的文本的值由Insert的value屬性來決定,在本例中,value屬性的表達式值是userName。你這時候可能會覺得越看越糊塗:那為什麼最後運行結果會是Jack呢?不要著急,關於這個我們馬上就要詳細地進行講解。我們還是先看一下最後一個元素context-asset,這個元素是定義這個Page組件要用到的一些附加資源,比如圖像、css格式文件等等之類的東西

現在我們開始認真地講一下<binding name="value" expression="userName"/>這個元素的含義,由於HTML模板嵌入的是一個Insert類型的組件,而Insert組件的功能是插入一段文本到HTML模板中去,對於本例而言,或許可以直接地指定要插入文本的內容為“Jack”,但對一個實際的應用來說,往往是要從數據庫從取出用戶的名稱,然後將該名稱插入到HTML模板中去,那麼就不能直接指定文本的內容了,怎麼辦呢?我們馬上想到的辦法是:指定一個JAVA類,通過javaBean的屬性方法來獲得數據庫中的用戶名,就像jsp中的<jsp getProperty … />標簽一樣,不錯,這的確是一個好方法,tapestry正是這樣做的,因此<binding name="value" expression="userName"/>這個元素中的expression的值正是demo.Home這個JAVA類的一個屬性,也就是說將demo.Home這個類的userName屬性綁定到Insert組件的value屬性中去,而我們的創始人Howard Lewis Ship先生並沒有重頭對這個綁定的功能進行設計,因為在軟件開發中有一句著名的話:”不要重復地發明同一個輪子“,這個綁定功能是由另一個開放源碼的工具完成地---- Object Graph Navigation Library,簡稱OGNL(關於OGNL具體可以參考http://www.ognl.org )。其內部真正的轉換如下圖所示:

其中,Insert這個JWC組件有一個名為value的屬性,而我們在Home.page中將它與一個外部對像userName進行綁定,在程序真正運行的時候,HTML模板中的user組件讀取demo.Home這個JAVA類的userName屬性,然後通過OGNL工具對Insert組件的value屬性進行更新,最後user組件將更新後的值插入到模板當中。好了,這時候你應該明白了Insert組件是怎樣取值的吧。

需要強調的是Page組件是一種特殊的JWC組件,它可以包含其它的JWC組件,但不能被其它的JWC組件所包含。除此之外,Page組件還有一些特殊的屬性和功能,它也不能和其它組件進行組合而生成新的組件。

組件的命名方式和HTML模板的命名方式有些不同,它可以以.page作為擴展名(如果是Page組件),或者以.jwc作為擴展名(如果是非Page組件)。組件一般放在當前web應用的WEB-INF目錄下面,以本例來說,我們的Home.page這個文件就應該放在webapps/Welcom/WEB-INF目錄下面。

Home.java

package demo;
import org.apache.tapestry.html.BasePage;
public class Home extends BasePage {
private String userName = "Jack";
public String getUserName() {
return this.userName;
}
}

最後一部分便是我們的demo.Home這個JAVA類,它實際上就是一個普通的javaBean,唯一不同的就是它必須從BasePage類中繼承。它的作用就是為HTML模板中的user組件提供文本信息的來源,在本例中我們簡單的返回”Jack”,但實際上getUserName方法還可以從JNDI、數據庫、EJB中取得用戶名。

其實demo.Home不僅僅只是提供數據來源,它還可以實現表單組件的提交。Tapestry提供了2個基本類方便用戶進行擴充,一個是BasePage類(專用於Page組件),另一個是BaseComponent類(用於用戶自定義組件)。通過繼承這些組件,可以大大地減輕用戶的編程量,從而把精力放到程序流程設計上去。

demo.Home組件類的名稱應該與Home.page中定義的名稱一致,它一般放在當前web應用的WEB-INF/classes目錄下,以本例來說,Home.class這個類應該放在webapps/Welcome/WEB-INF/classes/demo目錄下面。

Welcome.application

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE application PUBLIC
"-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
<application name="Tapestry Illustration 1">
<page name="Home" specification-path="Home.page"/>
</application>

以上這個文件是這個Tapestry應用的配置文件,它是以application作為根元素的,其中page元素指定了一個名為Home的Page組件,它的文件名為Home.page,當然你也可以指定更多的page組件,這個配置文件就像是一個總裝車間,把一個個page組件或其它的組件裝配起來。

關於這個配置文件的命名規范我們在前面已經講過了,它一般放在WEB-INF目錄下,在本例中它放在了webapps/Welcome/WEB-INF目錄下面。

web.xml

<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app>
<display-name>Tapestry Welcome Application</display-name>
<servlet>
<servlet-name>Welcome</servlet-name>
<servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Welcome</servlet-name>
<url-pattern>/app</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>15</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>

Listing 5. web.xml

這個文件相信所有的JSP/servlet開發員都非常熟悉了吧?在這個文件中我們定義了一個名為Welcome的servlet,它實際上就是org.apache.tapestry.ApplicationServlet這個類,它的主要作用是對客戶的請求進行包裝,然後轉發到各個page組件進行處理,當然如果你有特殊需要,也可以繼承ApplicationServlet類(當然這種情況很少出現)。

其中<servlet-mapping>用來進行地址映射,相信servlet開發員應該都知道它的作用吧?我們把所有對於/app的請求全部轉發給Welcome這個servlet進行處理,當然你也可以將/app換成其你自己的定義。最後我們打開一個IE浏覽器,在地址欄中輸入http://localhost:8080/Welcome/app 就會看到結果。

好了,現在我們就講一下關於上面那個用戶注冊的例子吧。由於篇幅的關系,我就不把所有的配置文件一一羅列了,只摘錄一些關鍵的配置。

CustInfo.html

<html jwcid="@Shell" title="Welcome Page">
<body jwcid="@Body">
<form jwcid="@Form" listener="ognl:listeners.submit">
Customer Name: <input jwcid="custName" type="text"/><br/>
Date-of-Birth: <input jwcid="dob" type="text" format="MMM dd, yyyy"/>
(Month DD, YYYY)<br/>
<input type="submit" value="Submit"/>
</form>
</body>
</html>

也許你看了上面這個文件不禁會問:為什麼有的組件名稱前面加了一個“@”?原因如下:tapestry由於是由眾多組件組成的,其中一般的組件都要在其Page組件中用<component>元素進行定義,對於一些簡單的或者無其它附加參數的組件來說就顯得比較麻煩,因此tapestry提出了顯式定義組件和隱式定義組件這個概念,顯式定義是指明確地在Page組件中定義過的組件,隱式定義是指沒有明確地在Page組件中定義的組件,都要在組件名字前加一個“@”。

這裡有一個組件顯得很特別:@Form組件,這個組件由於是隱式定義地,因此它的參數就直接在HTML模板中進行定義:listener="ognl:listeners.submit",它表示當用戶按下submit按鈕後頁面流程會交給當前Page組件類(即Welcome這個類)的submit函數進行管理。

下面我們再看一下它的Page組件的定義:

CustInfo.page

<?xml version="1.0" encoding="UTF-8"?>
<!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="demo.Welcome">
<property-specification name="custName" type="java.lang.String"/>
<property-specification name="dob" type="java.util.Date"/>
<component id="custName" type="TextField">
<binding name="value" expression="custName"/>
</component>
<component id="dob" type="DatePicker">
<binding name="value" expression="dob"/>
</component>
</page-specification>

其中值得關注的是dob這個組件,它的類型是tapestry核心組件庫中的DatePicker組件,有了它,我們就可以生成先前那個不可思義的選擇日期的界面了。

這裡出現了一個新面孔:property-specification元素,它是干什麼用的呢?還是先讓我們看一下那個“hello,world”例子中的組件類的定義吧:

Home.java

package demo;
import org.apache.tapestry.html.BasePage;
public class Home extends BasePage {
private String userName = "Jack";
public String getUserName() {
return this.userName;
}
}

其中userName這個屬性是一個javaBean屬性,通過get或set方法來存取userName的值,當然對於本例,property-specification這個元素和它一樣,就是定義了一個javaBean的屬性,其實你也可以像上例那樣進行存取,而不需在CustInfo.page中進行定義。定義property-specification元素的根本原因其實很可笑:為了偷一點懶! 為什麼這麼說呢?因為如果你用property-specification元素來定義javaBean的屬性的話,那你在組件類中就不必實現其get或set方法了,只需用一個抽像方法來完成,至於真正的get或set方法的實現就由tapestry來代勞了,其中,Welcome這個類的代碼如下:

Welcome.java
package demo;
import java.util.Date;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.html.BasePage;
public abstract class Welcome extends BasePage {
public abstract void setCustName(String custName);
public abstract void setDob(Date dob);
public abstract String getCustName();
public abstract Date getDob();
public void submit(IRequestCycle cycle) {
if (getCustName() != null
&& !getCustName().trim().equals("")
&& getDob() != null) {
Welcome welcome = (Welcome) cycle.getPage("Welcome");
welcome.setCustName(getCustName());
welcome.setDob(getDob());
cycle.activate(welcome);
}
}
}

前面的幾個抽像方法就不多說了,Welcome類的submit方法和在HTML模板中定義的@Form組件中的listener參數正好對應,也就是說,當用戶按下提交按鈕後,這個submit方法就會被激活,它進一步地引導著頁面下一步的動作。在本例中它先檢查用戶名和用戶出生日期(DOB,date of birthday的簡稱)是否為空,如果不為空,就將用戶在頁面中輸入的值賦予Welocome這個類的custName和dob這兩個屬性,然後激活welcome這個Page組件,也就是將頁面跳轉到welcome這個Page上去。

需要注意的是,Form組件定義的監聽類方法必須為public,並且要帶一個IRequestCycle 的參數,IrequestCycle是一個接口,它是由tapestry提供的一個對於用戶而言,當前會話的一個管理工具。

總結

到目前為止,你大概明白了Tapestry的原理了,但是一個復雜的Tapestry應用還是要考慮到很多問題的,比如頁面的定義,頁面之間的流程,組件元素的持久性設計,與EJB或原有的JSP系統的集成等等問題,但是Tapestry都已經為你考慮好了,你甚至可以把它的源碼下載下來仔細研究,也可以到它的郵件列表上發表自己的觀點,這正是我所喜愛的----open source ! 它代表著自由!

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