天天看點

在Turbine裡使用Velocity

出自:http://www.blogjava.net/kingwee/articles/237335.html

如果你從事Web應用程式開發已經很久了,那麼你可能已經意識到你正花費大量的時間在重新發明輪子。典型的任務包括資料庫連接配接池、建構控件Servlets、書寫導航菜單和設計衆多的頁面。重複使用在哪裡?元件?還是架構?幸運的是,許多人已經認真思考過這個問題,并建立了一些Web開發者需要的工具。在這一章裡,我們将介紹Turbine,它是一個試圖把Web開發帶入和傳統軟體開發相同的舞台的一個架構。為什麼這個架構對我們如此重要?原因就在于這個架構依賴Velocity作為MVC模式的view元件。

Turbine是什麼?

Turbine,Apache軟體開發組織旗下的Jakarta項目之一,是其衆多使用Velocity進行Web開發的可視化(visible顯著的)中的一個。它包含以下特性:

■它是一個基于Servlets的控制器元件

■它強調在應用程式中(比如購物車)固有的安全

■你可以在不依賴Web的環境下使用它

Turbine不僅僅是另一個應用程式伺服器,它還是一個應用程式架構(application framework),它為開發者提供了讓他們在建構企業級應用程式時所需要的工具,而不需要書寫大量的代碼。這并不是說,你可以把你的應用程式直接丢給伺服器。這個架構毫無疑問是一台主機,同時通過适當環境執行,比如Tomcat或Resin。Turbine為你建構特定Web應用程式提供了一些服務。另外,Turbine專用于在MVC模式中使用EJB、控制器Servlets和用Velocity寫螢幕的環境中。

随着我們開始尋求了解什麼是Turbine和它是怎麼和Velocity關聯的等問題答案的時候,Turbine變得越來越清晰,Turbine其實就是專門為Web開發者設計的。如果你是一個Web設計者,那麼你可能對Java代碼不是太熟悉,可能就需要一段艱苦的時間用于了解Java基礎知識,在這裡,哪怕你隻具備一點點Java基礎知識,你也可以通過我們提供的詳細步驟做完全部示例。Velocity的角色就是建構在Turbine裡需要使用的螢幕輸出。注意,Velocity不是你在Turbine裡唯一能夠用的模闆語言,Turbine也支援WebMacro、JSP、Cocoon和Freemarker。

有了這些概念之後,請系好你的安全帶,讓我們開始學習使用架構進行設計的激情之旅吧!

Turbine體系

首先,你需要一個圍繞Turbine體系進行設計的清晰圖表。Figure 14.1圖解了在這個架構裡的不同子產品。

在Turbine裡使用Velocity

Figure 14.1 The Turbine architecture.

正如你看到的一樣,這個架構包含了5個主要子產品,所有的子產品都在assemblers(彙程式設計式)的統一指揮下進行工作:

Action—完成特定任務的代碼

Navigation—顯示導航連結和控件的Velocity模闆

Screen—結合Velocity模闆和Java類,以顯示Layout子產品内的關鍵資訊

Layout—描述頁面外觀的Velocity模闆

Page—是一個概念上的對象,它包含了所有上面的子產品

下面我們将按順序解釋每一個子產品

Action模闆

正如你所期待的一樣,這個Action子產品是用于執行一個特定任務的一小塊Java代碼。其中最重要的任務就是執行将處理資訊以HTML<form>方式傳遞給使用者。在Action子產品裡的代碼将處理這些資訊,并指定給相應窗體,但一般而言,你知道,這些資訊還需要進行驗證、處理,可能的話,還需要進行持久化處理。Figure 14.2顯示了在Action和其他模闆之間的透視圖。

在Figure 14.2裡可以看到,Page子產品執行一個Action子產品以響應一個POST或GET請求。Action子產品和Page子產品進行“溝通(communicates)”,以确定适當的螢幕(screen)傳回給使用者。

如果你思考一下在此建立的範例,你可以很容易建立一個Action子產品的庫,一個可以在整個Web應用程式中重複使用的庫,而不需要為每一個頁面建立新的代碼。讓我們來考慮一下購物站點的設計,不管貨品出現在應用程式的哪裡,使用者都可以随時把貨品加入到購物車。在Action子產品裡為窗體書寫這個處理過程的代碼,你要確定相同的子產品不經過任何代碼更改就能夠在整個應用程式中使用。

在Turbine裡使用Velocity

Figure 14.2 The Action module flow.

在傳統的Web開發模型中,處理窗體的代碼一般在Servlets裡,并通過窗體進行調用。然而,在Action子產品裡,你可以在這個子產品裡保持商業邏輯和資料庫接口,進而把它們從控制器裡分離出來。如果你願意的話,你甚至可以使用EJB。

Navigation子產品

當你通路一個Web站點的時候,首先呈現給你的是一個頁面導航結構。這個結構可能在頁面的頂部,也可能在頁面底部,或在頁面的左邊。Turbine通過Navigation子產品來支援導航結構。一個Web應用程式可以有許多不同的導航結構,而且不限位置:可以是頂部、底部、頁面側面。Navigation子產品被用于處理這個導航結構,并這個結構是基于呈現給使用者的頁面細節進行變化的。通過某些機制來擷取資訊,比如EJB,這個子產品就擁有和資料庫通信的能力。稍後我們會讨論如何通過Layout子產品執行Navigation子產品。

Screen子產品

一個Web頁面包含了導航資訊和頁面有關的資訊。這個“重要”的頁面内容一般放在頁面的body裡。在Turbine裡通過使用Screen子產品來處理body。這個子產品主要負責渲染HTML标簽以回傳給使用者。對Screen子產品來說,讓其為使用者的特定資訊進行資料庫通路并不罕見。

Layout子產品

所有的Web頁面在向使用者呈現資訊的時候都有一個實體的布局或模闆。在某些情況下,相同的模闆可以用于整個應用程式或站點。在其他情況下,比如書店,例如,模闆基于呈現給使用者的頁面進行改變。在Turbine裡的布局是在Layout子產品裡定義的。這個子產品為Navigation子產品、body或在Screen子產品定義的頁面螢幕提供了一個占位符。Layout子產品主要負責執行Screen和Navigation子產品對頁面布局的依賴。

Page子產品

在極大程度上,Page子產品是其他子產品的容器,也是第一個從使用者浏覽器接受請求的子產品。如果請求包含了一個在Page子產品裡定義的action,Page子產品将會被執行。在完成action的執行後,Page子產品和Screen子產品進行通話,以确定将要執行的布局。

子產品對象封裝(Encapsulation)

Figure 14.3裡顯示了所有的子產品如何封裝在一起的。如果有使用者浏覽器請求,将調用Page子產品,這時,Page子產品執行一個裝入的Action子產品。接着,Page子產品繼續調用Layout子產品來決定要顯示哪一個螢幕。Layout子產品調用Navigation子產品來顯示這個頁面的導航資訊。最後,Screen子產品被執行,用于放置HTML格式的請求資訊到頁面上。

正如你所看見的一樣,Turbine架構把一個Web頁面作為一個對象集來進行顯示在頁面裡的。每一個對象隻負責其特定用于頁面的某一部分。

在Turbine裡使用Velocity

Figure 14.3 The Turbine modules.

Loaders

相對于我們之前讨論的5個子產品來講,Turbine定義了5個類,名叫loaders,每一個加載器負責加載其對應的特定子產品。Figure 14.4顯示了在Turbine裡的loaders層級圖。正如你所看到的一樣,加載器分别用于對應5個子產品。

在Turbine裡使用Velocity

Figure 14.4 Loader modules.

每一個類的成就都融入到Turbine的設計裡,這些加載器賦予他們一定程度的智能。在TurbineResources.properties裡,CLASSPATH變量被作為一個屬性進行定義,你可以定義一個特定路徑或設定路徑,用于讓loader嘗試定位資源。一旦loader從硬碟提取資源到記憶體的時候,loader有一個選項用于緩存這些子產品以友善下次再用。正如你所期待的一樣,多個loader可能嘗試加載同一個子產品,是以,所有子產品必須以線程安全方式進行書寫。

Turbine是如何工作的?

在我們深入讨論Turbine和建構一些應用程式之前。讓我們花一點時間來看一下使用Turbine系統進行頁面請求的處理過程。首先,我們大概描述一下test系統的布局。

使用者通過Web浏覽器發出一個請求,這個請求被分派給一個符合Turbine 控制器Servlets的URL。由于請求是使用的HTTP方式,是以這就需要在計算機上安裝一個傳統的Web伺服器。這個Web伺服器可以是Apache或Resin的内建HTTP伺服器。這個Servlets請求被轉移到一個在配置檔案時指定的應用程式伺服器上。

當Turbine Servlets從使用者那裡接收到這個請求時,它将對其進行檢測看是否是有一個目前使用者的HttpSession對象存在。一旦某個Turbine系統的功能是秘密的,那麼所有使用者都必須首先登入到應用程式中。這個HttpSession對象将傳回登入的結果。Turbine在應用程式中也使用cookie或帶有session資訊的URL。如果沒有HttpSession對象存在,系統将自動切換到登入頁面,這個切換過程是通過TurbineResources.properties進行配置來完成的。

Turbine Servlets的重要工作之一就是建立運作資料(Run-Data)對象。這個對象是非線程安全(non-thread-safe)的對象,用于Action、Screen和Document對象攜帶資訊,所有傳遞給Servlets的資訊都是通過Request對象進行傳遞的。

Turbine servlet嘗試通過檢測目前為使用者定義的action來決定使用者是否已經登入到應用程式中。如果這個action的值為LoginUser,則相應的action被執行。最後,一個名叫validateUser()的方法調用被執行,在這個方法裡,開發者書寫代碼依靠某些存儲方式(比如資料庫)驗證使用者的使用者名和密碼。在确認了使用者登入資訊後,使用者将直接傳回到登入螢幕或DefaultPage。所有這些工作都是通過SessionValidator action來完成的,而且它可以進行重載以支援更多功能。

當DefaultPage被執行時,它将對一個action進行檢測并執行它。完成這個action後,DefaultPage将詢問Screen子產品以找出DefaultPage的布局,找到後就執行它。Layout子產品執行Navigation和Screen子產品,最後,控件傳回給Turbine Servlets和一個新的HTML頁面被轉交給使用者。

擷取和安裝TDK

使用Velocity和Turbine的第一步就是擷取Turbine系統并安裝它。Turbine系統被分成幾個下載下傳。可能在單機模式下使用Turbine,最強大的方式是聯合使用Turbine、Velocity和一個應用程式伺服器(比如Tomcat)。你可以下載下傳以Turbine Development Kit(TDK)方式的需要重配置環境的Turbine,下載下傳位址是:ttp://jakarta.apache.org/ builds/jakarta-turbine/release/

在這個位址,你可以找到所有TDK的released版本。寫本書的時候,最新版為2.1。進行最新版的Turbine目錄,你将看到TAR.GZ(Unix/Linux)和zip檔案(Windows)的TDK、Turbine和Torque。由于我們隻想下載下傳TDK,是以,我們隻需下載下傳對象工作環境的TDK即可。

下載下傳完成後,解壓檔案到一個指定目錄。解壓完成後,将生成一個帶有衆多子目錄的/tdk目錄。

在測試安裝之前,你必須在你的系統上安裝兩個附加系統:

Ant—下載下傳URL為jakarta.apache.org/ant

JDK—下載下傳URL為sun.java.com

測試TDK安裝

測試TDK的安裝包括兩個步驟。

首先你必須配合TDK編譯示例應用程式,步驟為:進入/tdk目錄,執行Ant指令(簡單鍵入ant),Ant應用程式将基于/tdk目錄的build.xml檔案執行,過幾秒鐘後,建構完成。在很多情況下,建構是成功的。如果不成功,請檢查Turbine官方站點的郵件清單。

建構完成後,就可以進行系統測試了。進入/TDK根目錄下的/bin目錄。TDK的部分部件安裝已經完成,隻需要重新裝配應用程式伺服器Tomcat即可。雖然Turbine和Velocity都可以在Resin上使用(稍後讨論),但Tomcat是我們推薦的最佳應用伺服器。

在windows下用下面的指令啟動Tomcat

catalina.bat run

在Unix/Linux下用下面的指令啟動Tomcat

catalina.sh start

如果啟動成功,你将看到如下的資訊

Using CLASSPATH: .."bin"bootstrap.jar;c:"j2sdk1.4.1_01"lib"tools.jar

Starting service Tomcat-Standalone

Apache Tomcat/4.0-b6-dev

Starting service Tomcat-Apache

Apache Tomcat/4.0-b6-dev

Tomcat啟動後,在浏覽器輸入下面的URL來檢視TDK示例:

http://localhost:8080/newapp/servlet/newapp

如果運作成功的話,你可以看到Figure 14.5所示的輸出。

在Turbine裡使用Velocity

Figure 14.5 TDK sample application.

在這裡,你可以看到你安裝的TDK已經成功。如果你對TDK的應用程式示例感興趣,你可以,你可以使用下面的的URL來查閱相關文檔。

http://localhost:8080

你的第一個Turbine應用程式

安裝完TDK後,你就可以開始使用Turbine作為MVC架構、Velocity作為view元件來建構你自己的應用程式了。第一步是為Ant build檔案增加一個任務,并在build.properties裡增加一個适當的目錄結構。打開位于/tdk根目錄下的build.properties檔案,修改這個檔案裡的三個變量:

turbine.app—指定應用程式(在我們的示例裡為testApplication)

target.package—指定應用程式包(在我們的示例裡為:com.company. testApplication)

target.directory—指定将要建構的包的存放目錄(通常情況下為target.package,在我們的示例裡為:com/company/testApplication)

下一步,Ant将建構你的新應用程式。記住,Turbine隻是一個架構,是以你将得到許多自由的服務。Ant腳本移動不同的檔案到你指定的應用程式目錄。在下一節裡,我們将對這些放置到這個指定目錄的檔案進行研究。

Turbine(和你的應用程式)将使用一個資料庫。當我們建構一個新Turbine應用程式的時候,需要進入/tdk/webapps/<your application name>/WEB-INF/build目錄,并打開build.properties檔案,修改以下内容:

■定位database(資料庫)輸入,将其預設為mysql,并設定好相應的值。這個值會在Turbine建立SQL語句的時候使用。

■定位databaseUrll輸入,并增加适當的JDBC連接配接字元串,同時把JDBC驅動放到/WEB-INF/lib目錄。

■修改databaseDriver輸入為JDBC驅動類。

■修改databaseUser為資料庫登入使用者

■修改databasePassword為資料登入使用者密碼

■修改databaseHost為伺服器的IP位址

在這裡,系統必須為Turbine建立一個資料庫表。是以,進入/tdk/webapps/<your application name>/WEB-INF/build目錄,并執行ant init。這個Ant建構腳本将用你在build.properties屬性裡指定的内容連接配接到資料庫,并建立所需的表。當任意一個初始化命名被執行的時候,可能會發生許多錯誤。比如,build.xml檔案隻尋找Windows 98、Windows NT和Windows 2000,但如果你用的是Windows XP的話,隻需把所有的“98”修改成“XP”或為XP增加一個<target>元素。另外一個是在建構腳本嘗試通路資料庫指令行管理工具時可能會出現錯誤。請確定你的資料庫伺服器下的/bin目錄被包含進作業系統路徑(path)。

一旦Ant初始化指令成功執行,就可以嘗試編輯你的新應用程式了(記住,我們将使用Velocity來進行頁面表現)。為了檢視你的應用程式,請啟動Tomcat,之後浏覽下面的URL

http://localhost:8080/testApplication/servlet/testApplication

你将看到一個Turbine登入頁面,見Figure 14.6

為了通路你的新應用程式,請鍵入turbine作為你的使用者名和密碼。登入後的頁面見Figure 14.7

現在讓我們回頭看看你剛才建構的應用程式,看如何對其進行擴充來完成一些你想做的事情。

在Turbine裡使用Velocity

Figure 14.6 The Turbine login.

在Turbine裡使用Velocity

Figure 14.7 The Turbine main page.

解剖(Dissecting)這個應用程式

Okay,你已經安裝完成Turbine,并看到了示例的輸出。但它到底做了些什麼?好,讓我們來看一下這個應用程式,并看一下如何把我們所學的Velocity應用到進而去。讓我們從頭到尾看一下這個應用程式,并讨論幾個基礎類、方法和Turbine應用程式的功能調用。

當你在之前浏覽這個應用程式示例的時候,控件被傳遞給Turbine Servlets調用的普通Servlets。不管在這個Servlets裡到底發生了什麼,你需要幾個類型的HTML輸出來提供給用戶端。下面你将會看到,這裡僅有兩條路可以接觸伺服器,既通過連結或窗體。在第一情況下,你通過主應用程式URL來調用伺服器。

這個應用程式的URL首先被Web伺服器處理,這個Web伺服器可是内嵌型伺服器,也可以是獨立的Web伺服器。當URL最後傳遞到應用程式伺服器的時候,它将檢查它的主機清單,之後決定這個URL是否和他們比對。在這種情況下,Tomcat找到這個應用程式testApplication就是主機并存在對應的目錄結構。其中第一件Tomcat或Resin需要做的事就是打開web.xml檔案,并定位應用程式的/WEB-INF目錄。這是一個配置檔案,用于告訴伺服器當一個詳細的URL被發現的時候需要執行哪一個類。你的應用程式的web.xml檔案包含這些元素:

■ <servlet-name>testApplication</servlet-name>

■ <servlet-class>org.apache.turbine.Turbine</servlet-class>

這些元素告訴應用程式伺服器去執行在Turbine類裡找到的代碼。當Turbine Servlets在Turbine類裡被發現的時候正是如此。當Turbine Servlets執行的時候,它立即嘗試執行一個預設頁面。這個應用程式的預設頁面在/WEBINF/conf/TurbineResource.properties檔案裡進行定義。大約25%的情況下會設成

services.VelocityService/default.layout.template = /Default.vm

是以,因為在你的應用程式裡沒有布局或action(後面會介紹如何增加),是以Turbine Servlets使用了default.vm布局。這個default.vm布局(Listing 14.1)位于/templates/app/layouts目錄。

<table width="100%">

<tr>

<td colspan="2">

$navigation.setTemplate("/DefaultTop.vm")

</td>

</tr>

<tr>

<td width="20" align="left" valign="top">

$navigation.setTemplate("/Menu.vm")

</td>

<td align="left" valign="top">

$screen_placeholder

</td>

</tr>

<tr>

<td colspan="2">

$navigation.setTemplate("/DefaultBottom.vm")

</td>

</tr>

</table>

Listing 14.1 The default.vm Velocity template.

在你的應用程式晨,default.vm檔案是一個Layout子產品頁面。在這個檔案裡的代碼用于呈現将要傳回給使用者浏覽器的頁面内容。在這個頁面上沒有太多内容,你可以很容易的增加不同的可視化(look-and-feel)元件,比如一個顔色背景和插圖(和12章的CD應用程式一樣)。這個default.vm檔案包含了4個Velocity引用:

■第一個是位于頁面頂部的導航元件

■第二個也是導航元件,呈現為一個在頁面左側的導覽列

■第三個是一個螢幕元件,用作頁面的身體body

■第四個是位于螢幕底部的另一個導航元件

任何在這個模闆裡的改變将反映到輸出到使用者的頁面上。

在這裡,你或許會問,$navigation.setTemplate(String)方法到底做了些什麼?還記得Velocity允許得到依靠上下文裡的對象的方法調用嗎?$navigation.setTemplate(String)得到一個方法調用,setTemplate(String)依靠的是在上下文裡的$navigation對象。方法的調用結果将被放入模闆default.vm中,并傳回給使用者。現在,在default.vm模闆中的改動将被放入記憶體中,是以,它不是持久性的。在這種$screen_placeholder引用情況下,Velocity解析将嘗試用$screen_placeholder引用來進行替換這個引用。我們知道,在Turbine的處理流程中,Layout子產品調用适當的Screen子產品,并自動從Screen子產品傳回的代碼替換進$screen_placeholder裡。所有這些處理過程不需要任何人為幹預。其中的一些過程的了解或許有困難,是以一定堅持住,繼續探索究竟發生了什麼。

你或許會問$navigation和$screen_placeholder是在哪裡被替換的?這兩個引用使用的對象放置在Turbine Servlets支援的上下文中。實際上,有些替換過程是自動完成的,我們讓Turbine處理這些引用。是以,Turbine得到Layout檔案并嘗試去決定每一個引用。讓我們看一下導航元件中一個模闆檔案,是menu.vm模闆檔案的代碼,見Listing 14.2。

<font >

<a href="$link.setPage(" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" Insert.vm")">Insert Entry</a>

<p>

<b>Flux</b>

<br>

<a href="$link.setPage(" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" user,FluxUserList.vm")">Users</a>

<br>

<a href="$link.setPage(" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" group,FluxGroupList.vm")">Groups</a>

<br>

<a href="$link.setPage(" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" role,FluxRoleList.vm")">Roles</a>

<br>

<a href="$link.setPage(" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" permission,FluxPermissionList.

vm")">Permissions</a>

<p>

<b>Services</b>

##<br>

##<a href="" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >Intake Service</a>

##<br>

##<a href="" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >Localization Service</a>

##<br>

##<a href="" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >Pull Service</a>

##<br>

##<a href="" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >Scheduler Service</a>

<br>

<a href="$link.setPage(" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" Upload.vm")">Upload Service</a>

<br>

<a href="$link.setPage(" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" ServletInfo.vm")">Servlet Service</a>

<br>

##<a href="" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >Unique Id Service</a>

##<br>

##<a href="" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >XML-RPC Service</a>

##<br>

##<a href="" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >XSLT Service</a>

<p>

<b>Common Tasks</b>

<br>

<a href="" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >User Downloads</a>

<p>

<a href="$link.setPage(" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" Index.vm")">Home</a>

<p>

<a href="$link.setAction(" target="_blank" rel="external nofollow" LogoutUser")">Logout</a>

Listing 14.2 The menu.vm Velocity template. (continued)

這個menu.vm模闆包含相當多的Velocity代碼。兩個不同的調用了上下文。第一個是$link.setPage(String)方法調用。這個代碼通路了上下文中的“link”對象,并向其傳遞Velocity模闆的名稱,用于當使用者單擊連結的時候。當使用者不允許cookie時,系統将為這個使用者增加适當的路徑資訊并且有可能修改這個URL以滿足這種情況下的通路。$link.setPage(“Upload.vm”)調用産生一個如下所示的連結:

http://localhost:8080/TestApplication/servlet/TestApplication/template/Upload.vm

接下來是setAction(String),另一個用于上下文中連結對象的方法調用。這個方法也用于頁面的連結,但它不僅僅是放入一個連結,你将獲得一個聯合了action的連結。這個$link.setAction(“LogoutUser”)将産生下面的連結

http://localhost:8080/TestApplication/servlet/TestApplication/action/LogoutUser

正如你所期待的一樣,在一些窗體裡,使用者登出action是一個Java類,它是一個擴充自Action基類。正如你看到的一樣,通過這個示例應用程式的代碼,你可以看到下面的代碼

$link.setPage(“UploadComplete.vm”).setAction(“Upload”)

這種類型的調用會為一個模闆建立了一個連結,并且訓示一個必須執行的action。正如我們之前讨論的一樣,這個action将在頁面被建構之前執行。

現在你或許會驚訝填充$screen_placeholder的代碼來自哪裡?這裡有一小點竅門,$screen_placeholder的資訊是通過Screen子產品産生的。Screen子產品是使用兩個元件來建立的:一個Velocity模闆和一個Java類。這個模闆提供了一個可視化界面(look and feel),Java類為這個模闆增加引用到上下文給使用者。在本章的後面,我們将解釋為什麼這兩個元件都是必需的。為了讓系統能夠正常工作,模闆得到一個和Java類一模一樣的名稱。

那麼,當default.vm布局被使用的時候,既然Screen子產品并沒有在URL中指定,那麼它是從哪裡獲得的呢?答案是:Turbine的Resources.properties檔案在背景支援的。在這個檔案裡有兩個實體:

template.homepage=/Index.vm

template.login=/Login.vm

因為沒有在URL中指定螢幕,Turbine将自動使用index.vm螢幕。這個螢幕的代碼在webapps/TestApplication/templates/app/screens目錄下,具體内容見Listing 14.3。你的系統依靠你想顯示的螢幕,使用Velocity模闆來替換占位符。

$page.setTitle("Index")

$page.setBgColor("#ffffff")

#set ( $headings = ["Title", "Dept", "Author", "Url","Body"," "] )

#if ($entries)

<table>

<tr>

<td>

<table cellspacing="1" cellpadding="1">

<tr>

#foreach ($heading in $headings)

#headerCell ($heading)

#end

</tr>

#foreach ($entry in $entries)

<tr>

#entryCell ($entry.Title)

#entryCell ($entry.Dept)

#entryCell ($entry.Author)

#entryCell ($entry.Url)

#entryCell ($entry.Body)

<td><a href="$link.setPage(" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" Form.vm").addPathInfo("rdfid",

$entry.RdfId)">Edit</a></td>

</tr>

#end

</table>

</td>

</tr>

</table>

#end

Listing 14.3 The index.vm code. (continud)

在Listing 14.3裡的代碼是許多螢幕中的一個螢幕的代碼,讓你可以用作在default.vm頁面裡的$screen_placeholder的代替者。我們想指出在這個清單裡的幾件事。第一,注意這個頁面是用于顯示産生于資料庫的動态資料的,通過$entries reference。$entries reference被填充并且被加入到index.java頁面的上下文中,見Listing 14.4。因為這個Java類的名稱和模闆的名稱相同,是以,當index.java類被使用者浏覽器調用時,Turbine系統會自動使用index.vm。這就是index.java類的責任,既把适當的值放入$entries reference。第二,注意index.vm這個Velocity模闆,它使用了Velocimacros。#entryCell()是一個宏,它提供顯示表的格式。

package org.mycompany.newapp.modules.screens;

import java.util.Vector;

import org.apache.turbine.modules.screens.VelocityScreen;

import org.apache.turbine.util.RunData;

import org.apache.turbine.util.db.Criteria;

import org.mycompany.newapp.om.RdfPeer;

import org.apache.velocity.context.Context;

public class Index extends SecureScreen {

public void doBuildTemplate(RunData data, Context context) {

context.put("entries", getEntries());

}

private Vector getEntries() {

try {

Criteria criteria = new Criteria();

return RdfPeer.doSelect(criteria);

} catch (Exception e) {

return null;

}

}

}

Listing 14.4 Java code for the index.vm template.

正如你所看見的,index.java繼承自SecureScreen類,意思是這個Index類是一個螢幕,并且它需要使用者先登入系統後才能顯示。OK,讓我們在這兒停一下。這個SecureScreen類源自一個名叫VelocityScreen的Turbine基類。所有Screen子產品都繼承自VelocityScreen(如果需要驗證的話)。然而,所有的這些都依賴于我們之前列出來的template.login屬性。如果這兒有一個适當的模闆,并且為登入屬性聯合了Java類,那麼使用者必須先登入後才能使用這個模闆。如果你從template.login屬性中移去/login.vm,系統将不會提示你需要登入,因為它不知道把哪一個預設螢幕發給使用者,讓他登入到系統來。

讓我們回到index.java檔案。Index類的目的是建立一個$entries引用,以便讓index.vm模闆可以獲得資料并傳回給使用者。在VelocityScreen基類裡,SecureScreen類起源于一個名叫doBuildTemplate()的方法。當一個Screen子產品需要傳回它的模闆的時候,這個方法會被Turbine系統自動調用。是以,Turbine調用doBuildTemplate()方法,Screen子產品将把所有必須的Velocity引用放入到上下文中,并且所有關聯的模闆将獲得Turbine放置在$screen_placeholder引用裡的引用,用于最後傳回HTML,所有這些都很非常好、非常整潔。

在你運作代碼之前,先來看一看所有的平面輸出。讓我們花一點時間來解釋doBuildTemplate()方法和一些在Velocity模闆index.vm上的引用。雖然你不能看見他們,index.vm和index.java檔案查詢資料庫并顯示任何一個結果。你需要了解這個,以便你可以在你自己的代碼裡使用相同的技術。

從高層次(high level)來說,在index.java檔案裡的代碼把一個名叫$entries引用放入到上下文中,并且通過getEntries()方法傳回這個引用和它的值。getEntries()方法使用了一個Peer對象,這是一個助手對象,用于履行一個在資料庫上的選擇。資料庫結果行被放入上下文中。

OK,所有的用意是什麼?當Layout子產品調用index Screen子產品時,doBuildTemplate()方法被執行,它把一個名叫$entries的引用加入所提供的上下文對象中。通過Index類裡的getEntries()方法,$entries和它的值一起被擷取。雖然這個方法隻包含了兩行代碼,但有許多工作是在裡面完成的。

第一行代碼包含了一個Criteria類型的對象,Criteria類是一個助手類,用于從Turbine和一個名叫Torque的關聯包管理的資料庫擷取資訊。使用Criteria類,你可以放置過濾器(filter)或限制一個SELECT SQL指令執行。當沒有參數的執行個體或額外的方法在使用它的時候,對象将通知,一個SELECT将把所有的行和列從資料庫裡取出。Criteria對象的方法完整清單見現在這個URL:http://jakarta.apache.org/turbine/torque-3.0.0/apidocs/org/apache/torque/util/Criteria.html

在getEntries()裡的最後一行代碼傳回

RdfPeer.doSelect(criteria);

許多事情都發生在這個語句上,并且我們需要得到一個資料庫以避開對其進行說明。

在Turbine裡操作資料庫

所有在Turbine應用程式裡的資料庫都是在配置檔案裡定義的,這個配置檔案為/webapps/testApplication/WEB-INF/conf/testapplication-schema.xml,Listing 14.5顯示這個檔案裡的代碼

<database>

<table name="RDF">

<column name="RDF_ID" required="true" autoIncrement="true"

primaryKey="true" type="INTEGER"/>

<column name="TITLE" size="255" type="VARCHAR"/>

<column name="BODY" size="255" type="VARCHAR"/>

<column name="URL" size="255" type="VARCHAR"/>

<column name="AUTHOR" size="255" type="VARCHAR"/>

<column name="DEPT" size="255" type="VARCHAR"/>

</table>

</database>

Listing 14.5 The database schema XML.

從這個清單裡,你可以看到在Turbine裡定義一個資料庫非常容易。隻需要為一個特定的列(particular column)提供列、大小、類型和其他屬性。你可以通過以下指令增加一個表到你的資料庫

ant init

從/webapps/testApplication/WEB-INF/build目錄,當你做的時候,Ant執行了資料庫的所有在XML檔案裡定義好的建構任務。如果你關注一下Ant建構XML檔案的執行情況,你可以看到第一個操作是使用在XML檔案裡定義的表名稱DROP任意表。是以,所有已經存在的表的資料将全部丢失。你可以修改Ant任務或在執行任務之前先确認你的所有表定義。

Figure 14.8 shows a listing of the tables produced for the testApplication.

在Turbine裡使用Velocity

Figure 14.8 Tables for testApplication in MySQL.

當一個新表被建立的時候,會發生兩個主要的操作。第一個是,在資料庫配置裡定義的結構被寫入資料庫(Turbine配置的一部分)。第二個是執行處理SELECT、INSERT、DELETE和UPDATE的代碼,以及其他的資料庫操作任務。所有這些代碼被放在/webapps/testApplication/WEB-INF/src/java/com/company/testApplication/om目錄下。這個路徑com/company/testApplication依賴于你的應用程式名稱。

在這個目錄裡,你可以找到四個檔案,他們包含你的表的名稱。在RDF表的建立是通過Turbine使用Torque系統完成的,這些檔案是:

■ BaseRdf.java

■ BaseRdfPeer.java

■ Rdf.java

■ PdfPeer.java

BaseRdf.java檔案為資料庫行包含了所有的setter/getter方法。請不要修改這個檔案。Rdf.java檔案是一個從BaseRdf.java衍生而來的類,如果需要的話,可用于定義應用程式邏輯。BaseRdf-Peer.java檔案是一個助手類,它使用BaseRdf.java檔案來進行低級資料庫操作,其對象包含的方法可以完成所有普通的資料庫操作。RdfPeer.java檔案是一個從BaseRdfPeer.java衍生而來的類,在這裡可以放置應用程式邏輯,其用于代碼的對象将用于擷取或放置适當的表。

執行Select

基于我們已經呈現的資訊,getEntries()方法建立了一個Criteria對象,沒有任何表限制或結果傳回。接着,Criteria對象被提供給RdfPeer類的doSelect()方法。這個方法在RdfPeer類裡沒有重載,是以,一個調用建立了BaseRdfPeer類的doSelect()方法。這個方法基于傳遞進來的Criteria對象從資料庫裡重新得到行。

顯示結果

一旦getEntries()傳回,從RDF表傳回的行被作為$entries引用的值放到上下文對象中。在Index對象完成它的工作之後,Turbine Servlets将合并上下文和index.vm模闆,以為$screen_placeholder引用提供代碼。最後一步是傳回一個HTML頁面給使用者。

使用testApplicationAdding增加一個使用者

在從default.vm布局傳回給使用者的頁面和index.vm/java Screen子產品的左邊導覽列上都有許多的連結。第一個連結叫“Insert Entry”,其href屬性指定的是一個Velocity代碼。

<a href=”$link.setPage(“Insert.vm”)”>Insert Entry</a>

其對應的URL是

http://localhost:8080/testApplication/servlet/testApplication/template/Insert.vm

正如你看到的一樣,這個和我們最原始的URL比較想似,除了我們現在有一個新路徑資訊/template/Insert.vm外。當你單擊這個連結時,default.vm布局被使用,而不是預設的index.vm,你已經告訴Turbine Servlets,讓他使用由insert.vm和insert.java聯合定義的Screen子產品。現在讓我們來看一下Listing 14.6裡的insert.vm。

$page.setTitle("Insert")

<meta http-equiv="Content-Type" content="text/html; charset=iso-

8859-1">

</head>

<body bgcolor="#ffffff" leftmargin="0" topmargin="0"

marginwidth="0" marginheight="0">

<form

method="post"

action="$link.setPage("Index.vm").setAction("SQL")">

<div align="left">

<table bgcolor="#ffffff" cellpadding="5">

<tr>

#formCell ("Title" "title" "")

</tr>

<tr>

#formCell ("Author" "author" "")

</tr>

<tr>

#formCell ("Department" "dept" "")

</tr>

<tr>

#formCell ("Url" "url" "")

</tr>

<tr>

#formCell ("Body" "body" "")

</tr>

</table>

<input type="submit" name="eventSubmit_doInsert" value="Insert"/>

</div>

</form>

Listing 14.6 The insert.vm Velocity template.

我們已經介紹了Velocity模闆insert.vm裡面幾乎所有的東西。然而,這個時候,包含在<form>标簽裡的代碼。這個标簽用于從使用者和支援它的伺服器中提取資料庫的資料。這個窗體action包含了一個Turbine action子產品引用:

<form action=”$link.setPage(“Index.vm”).setAction(“SQL”)”>

Turbine action被找到并作為一個SQL action調用。用于告訴SQL action如何去做,這個輸入按鈕有一個名稱叫eventSubmit_doInsert,讓我們思考一下代碼是如何做的。首先,使用者單擊Insert Entry連結,Turbine servlet提出預設的布局default.vm用于渲染适當的Navigation子產品,除了調用insert.java類的doBuildTemplate()方法外。然而,如果你看一下/src/com/mycompany/testApplication/modules/screens/目錄,你就會發現insert.java的源檔案。這就提出了一個好的點子,Screen子產品不再需要由VM和Java類檔案組成。如果一個Screen子產品隻有一個VM模闆,Turbine伺服器隻需簡單渲染Velocity模闆而不需要任何上下文的改變。如果僅包含了一個Screen類,這個類将負責提供所有的輸出支援,因為這個沒有Velocity模闆來渲染HTML輸出。

在這裡,Velocity模闆insert.vm基于目前上下文對象被渲染為$screen_placeholder引用。在這點上,使用者在<form>輸出字段輸入資料并單擊送出按鈕。這個單擊将導緻Turbine servlet執行一個Action子產品來調用SQL。記住在Layout對象之前,所有的action都将被執行。

SQL action在modules/actions目錄下。這個action的源檔案在/src/com/mycompany/testApplication/modules/actions目錄下的SQL.java檔案(Listing 14.7)。

import org.apache.velocity.context.Context;

import org.apache.turbine.util.RunData;

import org.apache.turbine.util.db.Criteria;

import org.apache.turbine.modules.actions.VelocityAction;

import com.company.testApplication.om.Rdf;

import com.company.testApplication.om.RdfPeer;

public class SQL extends SecureAction {

public void doInsert(RunData data, Context context)

throws Exception {

Rdf entry = new Rdf();

data.getParameters().setProperties(entry);

entry.save();

}

public void doUpdate(RunData data, Context context)

throws Exception {

Rdf entry = new Rdf();

data.getParameters().setProperties(entry);

entry.setModified(true);

entry.setNew(false);

entry.save();

}

public void doDelete(RunData data, Context context)

throws Exception {

Criteria criteria = new Criteria();

criteria.add(RdfPeer.RDF_ID, data.getParameters().getInt("rdfid"));

RdfPeer.doDelete(criteria);

}

public void doPerform(RunData data, Context context)

throws Exception {

data.setMessage("Can't find the button!");

}

}

Listing 14.7 The SQL Action class.

SQL Action用于SecureAction。通過使用SecureAction基類,隻有當使用者登入進系統後,這個action才會被執行。送出按鈕使用了一個doInsert的值,在doInsert()方法裡,系統利用全局runData對象,在這裡,來自<form>的參數被定位。在這種情況下,你不能為RDF表使用Peer對象,但可以使用RDF類自身。這個來自<form>的值被替換成RDF對象并存入資料庫。

一旦SQL Action把新資訊存儲到适當的資料庫,使用它的Navigation子產品來執行預設的布局,同時index.vm螢幕在<form action>裡指定。Figure 14.9顯示了我們向資料庫增加一個新實體後的情況,Figure 14.10顯示了testApplication頁面。

在Turbine裡使用Velocity

Figure 14.9 A database entry in MySQL.

在Turbine裡使用Velocity

Figure 14.10 The testApplication page after the insert.

注意在Figure 14.10螢幕右上邊的“edit”連結。如果你把滑鼠移至連結的上方,你可以看到現在的連結

http://localhost:8080/testApplication/servet/testApplication/tempalte/Form.vm/rdfid/1

在這裡,你應該知道當你單擊這個連結的時候,這些代碼打算做些什麼。form.vm和form.java Screen子產品檔案被調用。這個時候,你需要有一個form.java檔案。正如你料想的一樣,在URL末尾的rdfid/1資訊将用于從資料庫提取行并顯示它,見Figure 14.11。Form類的代碼見Listing 14.8。

在Turbine裡使用Velocity

Figure 14.11 An edit on the database row.

public class Form extends SecureScreen {

public void doBuildTemplate( RunData data, Context context {

try {

int entry_id = data.getParameters().getInt("rdfid");

Criteria criteria = new Criteria();

criteria.add(RdfPeer.RDF_ID, entry_id);

Rdf rdf = (Rdf) RdfPeer.doSelect(criteria).elementAt(0);

context.put("entry", rdf);

} catch (Exception e){

}

}

}

Listing 14.8 The form.java code.

正如你所知道的一樣,Screen子產品代碼在Velocity模闆之前執行。是以在Listing 14.8裡的doBuildTemplate()代碼通過連結獲得主要關鍵字,并用于提取資料庫行、替換上下文中的Velocity模闆form.vm來顯示給使用者。在連結末尾的rdfid被轉換成一個參數,并且被放到RunData對象中。之後,它使用Criteria對象來定位資料庫,并從資料庫提取資料行。elementAt(0)方法被用于把一個資料行中放入上下文的“entry”引用中。

注意那些action是不傳回結果的。當你需要從資料庫中得到資訊的時候,隻需為Screen子產品使用一個類就行。如果這兒有你需要的action來傳達,你可以通過使用data.setMessage(String)指令在RunData對象中設定一個消息變量。

當書寫你的action的時候,你需要從VelocityAction和Secure Action類(依賴于是否需要進行登入驗證)開始。在任意一種情況下,這個action都包含了一個或多個ActionEvent。正如你在SQL Action裡看到的,那裡的事件都用于送出按鈕的值和一名名叫doPerform(RunData, Context)的方法。doPerform()方法是一個action的預設方法。如果沒有其他的方法可用或在單擊窗體上的按鈕沒有比對的ActionEvents(在類裡)時,doPerform()方法将會被執行。

重建和部署

一旦你修改了你的應用程式,你就必須重建它。在目前的TDK環境下非常容易,隻需進入/webapps/testApplication/WEB-INF/build目錄,并執行下面的指令

ant compile

最後,當Turbine在Tomcat上運作時,就不再需要它了。如果你想轉移到Resin平台或BOSS或其他應用程式伺服器,隻需要打包你的testApplication或其他應用程式名稱目錄結構并在别處進行部署就行。你或許會需要删除或調整在web.xml檔案中DTD。

在Turbine裡使用Velocity的進階功能

最後一節比較簡略,我們将介紹在Turbine裡使用Velocity的許多主要特性。然而,我們想介紹一點附加的進階特性。當然,你可以查閱Turbine文檔以獲得更多資訊。

RunData對象

在整個示例應用程式中,我們談到了RunData對象,并且通過使用資料關鍵字擷取了一個該對象的引用。RunData對象有許多方法和許多有益于開發的字段。既然RunData對象在模闆和許多子產品Java類中可以使用,那麼開發者就應該充分利用這個對象。該對象的一些方法為:

void setMessage(String msg)—在RunData對象裡設定一條消息,這個常用于錯誤處理和其他子產品間的資訊傳遞,比如Action子產品

string getMessage()—傳回一條RunData資訊

parameter Parser getParameters()—傳回從一個HTML<form>或在URL連結上的參數

void setTitle(String)—指定頁面标題,常常在Velocity Screen子產品模闆裡用作第一個語句

user getUser()—得到目前在session裡的使用者

boolean userExists()—檢查在session裡是否存在目前使用者

boolean removeUserFromSession()—使目前在session裡的使用者無效

void setRedirectUri(String ruri)—為重定向(redirection)設定URL

如何你正在Screen子產品Java類裡處理資訊,并且你想更改Screen子產品的模闆,你可以使用getTemplateInfo()方法來擷取一個TemplateInfo對象,并且使用setScreenTemplate(String)方法來設定一個新的動态模闆。

TemplateLink對象

在所有的Velocity模闆中,你可以使用TemplateLink對象來建立一個新連結到另外一個模闆,這個對象源自DynamicURI。新連結通過下面的代碼來加入:

$link.setPage(String);

$link引用是一個TemplateLink對象,它和DynamicURI一起擁有一個巨大的方法清單和用于建構動态連接配接的字段。所有在我們示例中的這些連結都是指向一個動态頁面。我們也可以使用下面的指令連結到一個靜态頁面

$link.setPage(“/docs/static/privacy.html”)

這個新的靜态頁面是一個使用者Web浏覽器的沒有任何導航或布局控件的首頁面。最後的操作是在Velocity模闆裡使用靜态頁面,并且維持Web應用程式的界面。

TemplatePageAttributes對象

當$page引用被用于一個Velocity模闆時,TemplatePageAttributes對象被擷取。這個對象允許目前頁面的元素被更改,比如标題和背景顔色。比如:

$page.setBgColor(“#FF0000”)

這個指令将産生一個紅色背景,你允許通過addAttribute(string, int)指令通路<body>标簽。其他可用的指令包括:

addAttribute(String name, String value)—在body标簽裡增加屬性 “name”和“value”

setBackground(String url)—設定背景 URL.

setDescription(String description)—設定聲明(description)标簽

setKeywords(String description)—設定一個關鍵字(keyword)标簽

setLinkColor(String color)—指定連結顔色

setStyleSheet(String url)—設定一個CSS(stylesheet)

setTextColor(String color)—指定文本顔色

setVLinkColor(String color)—指定vlink顔色

本章小結和下章介紹

在本章,你了解到,在Turbine驅動的Web應用程式的開發裡,Velocity是一個主要的元件。在下一章裡,我們将介紹另一個架構,名叫Maverick。

繼續閱讀