项目的格局遵循的是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猜谜游戏