項目的格局遵循的是Maven倡導的一個很合適的標準:html
l Java源代碼文件放在 src/main/java 下面java
l Web應用程序文件放在 src/main/webapp(包括src/main/webapp/WEB-INF)web
l Java測試資源放在src/test/java下面ajax
l 非代碼資源(包括Tapestry頁面和組件模板)放在src/main/resources和src/test/resources下面apache
讓咱們來看看Maven根據原型建立了寫什麼,先從web.xml配置文件開始:瀏覽器
src/main/webapp/WEB-INF/web.xml安全
<?xml version="1.0" encoding="UTF-8"?>app
<!DOCTYPE web-app框架
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"webapp
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>tutorial1 Tapestry 5 Application</display-name>
<context-param>
<!-- The only significant configuration for Tapestry 5, this informs Tapestry
of where to look for pages, components and mixins. -->
<param-name>tapestry.app-package</param-name>
<param-value>com.example.tutorial1</param-value>
</context-param>
<!--
Specify some additional Modules for two different execution
modes: development and qa.
Remember that the default execution mode is production
-->
<context-param>
<param-name>tapestry.development-modules</param-name>
<param-value>
com.example.tutorial1.services.DevelopmentModule
</param-value>
</context-param>
<context-param>
<param-name>tapestry.qa-modules</param-name>
<param-value>
com.example.tutorial1.services.QaModule
</param-value>
</context-param>
<filter>
<filter-name>app</filter-name>
<filter-class>org.apache.tapestry5.TapestryFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>app</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
這個文件比較簡短:你能夠看到本身早先提供的包名做爲 tapestry.app-package 上下文參數顯示在這個文件中;TapestryFilter實例會利用這個信息訂購page和component的Java類。
Tapestry操做的是一個servlet Filter而不是傳統的servlet。用這種方法,Tapestry就有機會攔截到全部的傳入請求,以據此決定哪一個請求對應到哪一個Tapestry頁面(或者其它的資源)。最終的效果就是你沒必要爲Tapestry維護任何額外的配置了,不管你要嚮應用程序添加多少page和component。
web.xml剩餘的大部分都是配置用來匹配Tapestry執行模式對應的模塊類的。執行模式定義了應用程序將如何運行:默認的執行模式是「production」,不過web.xml還定義了兩個模式:「development」和「qa」(表明「Quality Assurance」)。模塊類將會針對這些執行模式而被加載,並能以各類方式修改應用程序的配置。本教程稍後會回過頭來再來說這個執行模式和模塊類。
Tapestry的page至少包含一個普通的Java類和一個組件模板文件。
在web應用程序的根目錄,有一個叫作「Index」的page江北被用於任何沒有在上下文名稱後面指定額外路徑的請求。
Tapestry對於哪裏放置page類有很是特殊的規定。Tapestry將一個子包,「pages」添加到了應用程序根包(「com.example.tutorial1」)下面;用於page的Java 類就放在這兒。一次Java類的全稱就是com.example.tutorial1.pages.Index。
src/main/java/com/example/tutorial/pages/Index.java
package com.example.tutorial1.pages;
import org.apache.tapestry5.Block;
import org.apache.tapestry5.EventContext;
import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.annotations.InjectPage;
import org.apache.tapestry5.annotations.Log;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.services.HttpError;
import org.apache.tapestry5.services.ajax.AjaxResponseRenderer;
import org.slf4j.Logger;
import java.util.Date;
/**
* Start page of application tutorial1.
*/
public class Index
{
@Inject
private Logger logger;
@Inject
private AjaxResponseRenderer ajaxResponseRenderer;
@Property
@Inject
@Symbol(SymbolConstants.TAPESTRY_VERSION)
private String tapestryVersion;
@InjectPage
private About about;
@Inject
private Block block;
// Handle call with an unwanted context
Object onActivate(EventContext eventContext)
{
return eventContext.getCount() > 0 ? new HttpError(404, "Resource not found") : null;
}
Object onActionFromLearnMore()
{
about.setLearn("LearnMore");
return about;
}
@Log
void onComplete()
{
logger.info("Complete call on Index page");
}
@Log
void onAjax()
{
logger.info("Ajax call on Index page");
ajaxResponseRenderer.addRender("middlezone", block);
}
public Date getCurrentTime()
{
return new Date();
}
}
在這份代碼裏面有許多東西,Index page想要精力向你呈現Tapestry中一堆不一樣的理念。即便如此,這個類看起來仍是至關的簡單:Tapestry的page和component沒有積累須要擴展,也沒有接口須要實現,而僅僅只是一個純粹的POJO(Plain Old Java Object)……屬性和方法都帶有一些特殊的命名約定和註解。
這其中你必須得知足Tapestry框架的要求:
l 須要把Java類放在預約的包中,這裏就是com.example.tutorial1.page
l 類必須是public的
l 須要確保有一個public的,沒有參數的構造器(這裏Java編譯器已經悄悄地爲咱們提供了一個)
l 全部的非靜態屬性都必須是private的
在運行這個應用程序時,如咱們所見,page會展現當前的日期和時間,還有一些額外的連接。currentTime屬性就是這些值的來源;很快咱們會明白這個值是如何被模板引用到的,那樣它就能夠從page和輸出那裏獲取到了。
Tapestry老是會把page對應到一個模板;它們缺乏了對方都是沒有用的。事實上,一個page中的component也是以一樣的方式被對待的(此外component並不老是有對應的模板)。
你回經常聽到模型-視圖-控制器模式(MVC)。模板就是MVC中視圖。而做爲模型的page會暴露出能夠在模板中被引用到的JavaBean。
讓咱們來看看component模板是如何在Java類上構建出完整的用戶界面的。
Tapestry的page是一個POJO Java 類同一個Tapestry component模板的組合。模板會有跟Java類相同的名稱,不事後綴名會是.tml。由於這裏的Java類是com.example.tutorial.pages.Index,因此模板文件就會被放置在src/main/resource/com/example/tutorail/pages/Index.tml。最終,Java類和component 模板文件都會被存儲在用於部署的WAR文件的同一個目錄之中。
Tapestry的component模板是形式良好的XML文檔。這就意味着你能夠利用上任何可用的XML編輯器。模板可能甚至會有一個DOCTYPE或者一個XML schema來驗證模板page的結構是否正確。
注意Tapestry回用一個非驗證性質的解析器來解析component模板:它只會檢查形式是否良好:正確的語法,對應平衡的元素,屬性值是在雙引號中,注入此類。你本身決定要不要構建流程來執行某些類型的模板驗證,而只要能順利的解析,Tapestry仍是會照常接受模板。
Tapestry component模板的大部分都像是一個普通的XHTML:
src/main/resources/com/example/tutorial1/pages/Index.tml
<html t:type="layout" title="tutorial1 Index"
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd"
xmlns:p="tapestry:parameter">
<div class="hero-unit">
<p>
<img src="${asset:context:images/tapestry.png}"
alt="${message:greeting}" title="${message:greeting}"/>
</p>
<h3>${message:greeting}</h3>
<p>The current time is: <strong>${currentTime}</strong></p>
<p>
This is a template for a simple marketing or informational website. It includes a large callout called
the hero unit and three supporting pieces of content. Use it as a starting point to create something
more unique.
</p>
<p><t:actionlink t:id="learnmore" class="btn btn-primary btn-large">Learn more »</t:actionlink></p>
</div>
<div class="row">
<div class="span4">
<h2>Normal link</h2>
<p>Clink the bottom link and the page refresh with event <code>complete</code></p>
<p><t:eventlink event="complete" class="btn btn-default">Complete»</t:eventlink></p>
</div>
<t:zone t:id="middlezone" class="span4">
</t:zone>
<div class="span4">
<h2>Ajax link</h2>
<p>Click the bottom link to update just the middle column with Ajax call with event <code>ajax</code></p>
<p><t:eventlink event="ajax" zone="middlezone" class="btn btn-default">Ajax»</t:eventlink></p>
</div>
</div>
<t:block t:id="block">
<h2>Ajax updated</h2>
<p>I'v been updated through AJAX call</p>
<p>The current time is: <strong>${currentTime}</strong></p>
</t:block>
</html>
你必定得用跟component 類的名稱Index的每一個字母都同樣的名稱來命名你的component模板文件,也就是Index.tml。若是有一個字符錯了,就有可能在某些操做系統(好比Mac OS X,Windows)上仍然有效,不過在其它的(Linux和大多數其它的)上面就不行了。這可真使人煩心,由於在Windows上面作開發,而部署到Linux和Solaris上面是很常見的事情,因此這一處仍是當心爲是。
在Tapestry中,對於諸如Index.tml這樣的component模板,其目標就是儘量想一個普通的,靜態的HTML文件。(這裏的靜態static的意思是對比一個動態生成的Tapestry page,不用修改的意思)。
事實上,在許多狀況下咱們所指望的是,模板開始是一個靜態的HTML文件,有web開發者建立出來,而後被組裝做爲一個動態的Tapestry page。
Tapestry在XML命名空間裏面隱藏了非標準的元素和屬性。按照約定,前綴「t:」被用於主命名空間,不過這不是必須的,任何你想要使用的前綴均可以。
這個簡短的模板展現了Tapestry至關多的特性。
Quickstart原型的部分意圖是展現一堆不一樣的功能特定、方法以及在Tapestry被用到的通用模式。所以確實咱們是在一次性地用所有的東西來打動你。
首先,有兩個XML命名空間是一般都要被定義的:
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd"
xmlns:p="tapestry:parameter"
第一個命名空間,「t:」,被用來識別Tapestry特定的元素和屬性。儘管有了一個XSD(就是一個XML schema定義),不過不完整(緣由很快就會解釋)。
第二個命名空間,「P:」,是一種把至關多的模板標記爲一個參數傳入到另外的組件的方式。很快咱們會展開來描述這個東西。
Tapestry component模板包含大多數標準的XHTML,它們不作修改就會被向下傳遞給玩野瀏覽器。模板的動態部分由component和expansion來呈現。
讓咱們從exansion開始。Expansion是在渲染頁面時包含一些動態輸出的簡便方式。Expansion默認會引用page的JavaBean中的屬性。
<p>The current time is: ${currentTime}</p>
大括弧中的值是一個屬性表達式。Tapestry使用其本身的屬性表達式語言,富有表現力,快速,且類型安全。
Tapestry並無使用反射來實現屬性表達式。
更高級的屬性表達式能夠橫向引用多個屬性(例如:user.address.city),或者甚至調用公共方法。這裏的expansion簡單的讀取了page的currentTime屬性。
Tapestry遵行有Sun JavaBean規範定義的規則:屬性的名稱currentTime映射到兩個方法:getCurrentTime()和setCurrentTime()。若是你省略了其中一個方法或者兩個都省略掉,屬性就會是隻可讀的(如這個示例中所示),或則是隻可寫的。(請牢記,不用管什麼JavaBean的屬性,它的方法纔是關鍵;實例標量的名稱,以致於它們是否存在,都可有可無。)
Tapestry則更近一步:在匹配expansion中的屬性到page的屬性時,它會忽略大小寫。在模板中咱們能夠用${currenttime}或者${CurrentTime}或者其它變體,而Tapestry仍舊是調用getCurrentTime()方法。
注意在Tapestry中不必設置有什麼對象擁有currentTime屬性;一個模板或者一個page老是會組合在一塊兒互相利用;表達式老是以page實例爲根,在這種狀況下,就是Index類的一個實例。
Index.tml模板包含的第二個expansion:
<p>${message:greeting}</p>
這裏greeting並不是是page的一個屬性;它其實是一個本地的消息鍵。每一個Tapestry page和component均可以擁有其本身的消息目錄(message catalog)。(還有一個全局的消息目錄,稍後咱們會描述一下。)
src/main/resources/com/example/tutorial/pages/Index.properties
greeting=Welcome to Tapestry 5! We hope that this project template will get you going in style.
消息目錄對於在代碼或者模板以外存儲重複的字符串時很是有用,儘管主要目的同應用程序的本地化有關(這會在稍後的章節中有詳細描述)。可能被多個page用到的消息能夠被存儲在應用程序的全局消息目錄中,也就是src/main/webapp/WEB-INF/app.properties。
這個「message:」前綴並非某種特殊狀況;實際上有一些這樣的綁定前綴被構建到了Tapestry,每個都有特殊的目的。事實上,表達式中忽略半丁前綴就跟使用了「prop:」是同樣的,意思是將綁定看做是一個屬性表達式。
Expansion在用來獲取一塊信息並將其做爲字符串渲染到客戶端時是頗有用的,不過Tapestry中重要的擔子都放在了component裏面。
Component以兩種方式在component模板中表示:
l 做爲一普通的元素,不過帶有一個t:type屬性,用來定義component的類型。
l 做爲Tapestry命名空間中的一個元素,這種狀況下元素的名稱決定其類型。
這裏咱們使用了一個<html>元素來表示應用程序的Layout(佈局)component。
<html t:type="layout" ...>
...
</html>
而對於 EventLink component,咱們使用了Tapestry 命名空間中的一個元素:
<t:eventlink page="Index">refresh page</t:eventlink>
選哪中形式就是一種選擇而已。在多數狀況下,二者幾乎是同樣的。
跟其餘地方同樣,大小寫是無關的。這裏的類型(「layout」和「eventlink」)都是用的小寫;實際的類名稱是 Layout 和 EventLink。Tapestry會進一步將核心庫的component同這個應用程序定義的component「混淆」;如此類型「layout」會被映射到應用程序的component類com.example.tutorial.components.Layout,而「eventlink」會被映射到Tapestry內置的org.apache.tapestry5.corelib.components.EventLink類。
Tapestry的component是使用參數來配置的;對於每一個component,都有一堆參數,每個都帶有一個特殊的類型和目的。某些參數是必需的,其它是可選的。元素的屬性被用來將參數綁定到特定的字面值,或者是page的屬性。Tapestry在此處是很靈活的;你老是可以將屬性放到Tapestry的命名空間中(使用「t:」前綴),不過在大多數狀況下,不必這麼作。
<html t:type="layout" title="tutorial1 Index"
p:sidebarTitle="Framework Version" ...
這個將Layout component的兩個參數,title和sidebarTitle對應綁定到了字面值「tutorial Index」和「Framework Version」。
Layout component將實際給瀏覽器發送最終的HTML;咱們將會在稍後的章節中查看這個模板。這裏要點是,page的模板被集成到了Layout component的模板中。下圖展現了參數是如何被傳到Layout component並被渲染成最終的頁面的:
這裏有點意思(也是Tapestry中的一個高級概念,稍後回頭來再講)的是咱們能夠見Index.tml的一塊做爲sidebar參數傳入Layout component。這就是tapestry:parameter命名空間(也就是「p:」前綴)的用處;元素的名稱被匹配到component的一個參數,而template的一整快被傳入了Layout component……它決定了在其模板的哪一個位置渲染這一塊。
<t:eventlink event="complete" class="btn btn-default">Complete»</t:eventlink>
這一次是PageLink component的page參數被綁定到了字面值「Index」(就是這個page的名稱)。其或被渲染成一個從新渲染這個page的URL,解釋了當前時間是如何被更新的。你也能夠建立到應用程序中其它page的連接,稍後的章節中咱們會看到,且除了page名稱意外,還能夠將額外的信息附加到URL上。
如今是時候玩一個魔術小把戲了。修改Index.java,將getCurrentTime()方法修改爲:
Index.java (部分)
public String getCurrentTime()
{
return "A great day to learn Tapestry";
}
確保你對修改進行了保存,而後在網頁瀏覽器中點擊刷新:
這是Tapestry早期的一個使人叫絕的特性,對component類的修改能夠當即生效(一個咱們稱做動態類從新加載Live Class Reloading的特性)。無需重啓。也無需從新部署。作出修改而後立刻就能看到效果。沒有什麼能拖你的後腿,或者當你的道兒。
若是Live Class Reloading不起做用,看看Class Reloading中的Troubleshooting一節。
不過……要是你代碼寫錯了呢?若是你把模板中的名稱搞錯了會怎樣。能夠試一試;就在模板中,將${currentTime}改爲比方說${currenTime},看看你會獲得什麼結果:
這是Tapestry的異常報告頁面。它至關的詳細。清楚的指明Tapestry正在作什麼,還將問題同模板中的特定行關聯起來,在上下文中顯示出來。Tapestry老是展開顯示整個異常跟蹤棧,由於異常的拋出、捕獲和在其餘異常中從新拋出是如此廣泛。事實上,若是咱們將頁面向下只是滾動一點點,就能夠看到有關這個異常的更多信息,還有一點點幫助信息:
這就是Tapestry行事方式的一部分:不只指出正在作的是什麼還有出了什麼問題,甚至於還要幫助你找到解決方案;在這裏它告訴你應該已經使用過的屬性名稱。
其詳細程度代表應用程序已經被配置成development模式而不是production模式。在production模式中,異常報告只會簡單的顯示頂層的異常消息。不過,大多數應用程序更進一步,會對Tapestry如何處理和報告異常進行自定義。
Tapestry會顯示最深層異常的跟蹤棧,與此同時還有許多關於運行時華景的詳細信息:關於當前請求,HttpSession(若是存在一個的話)的詳細信息,甚至還會有全部JVM系統屬性的詳細清單。往下滾動就能夠看到全部的這些信息。
接下來是:實現Hi-Lo猜謎遊戲