Tapestry 教程(四)探索项目结构

项目的格局遵循的是Maven倡导的一个很合适的标准:html

Java源代码文件放在 src/main/java 下面java

Web应用程序文件放在 src/main/webapp(包括src/main/webapp/WEB-INFweb

Java测试资源放在src/test/java下面ajax

非代码资源(包括Tapestry页面和组件模板)放在src/main/resourcessrc/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实例会利用这个信息订购pagecomponentJava类。

Tapestry操做的是一个servlet Filter而不是传统的servlet。用这种方法,Tapestry就有机会拦截到全部的传入请求,以据此决定哪一个请求对应到哪一个Tapestry页面(或者其它的资源)。最终的效果就是你没必要为Tapestry维护任何额外的配置了,不管你要向应用程序添加多少pagecomponent

web.xml剩余的大部分都是配置用来匹配Tapestry执行模式对应的模块类的。执行模式定义了应用程序将如何运行:默认的执行模式是“production”,不过web.xml还定义了两个模式:“development”和“qa”(表明“Quality Assurance”)。模块类将会针对这些执行模式而被加载,并能以各类方式修改应用程序的配置。本教程稍后会回过头来再来说这个执行模式和模块类。

Tapestrypage至少包含一个普通的Java类和一个组件模板文件。

web应用程序的根目录,有一个叫作“Index”的page江北被用于任何没有在上下文名称后面指定额外路径的请求。

Index Java 

Tapestry对于哪里放置page类有很是特殊的规定。Tapestry将一个子包,“pages”添加到了应用程序根包(“com.example.tutorial1”)下面;用于pageJava 类就放在这儿。一次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中一堆不一样的理念。即便如此,这个类看起来仍是至关的简单:Tapestrypagecomponent没有积累须要扩展,也没有接口须要实现,而仅仅只是一个纯粹的POJOPlain Old Java Object)……属性和方法都带有一些特殊的命名约定和注解。

这其中你必须得知足Tapestry框架的要求:

须要把Java类放在预约的包中,这里就是com.example.tutorial1.page

类必须是public

须要确保有一个public的,没有参数的构造器(这里Java编译器已经悄悄地为咱们提供了一个)

全部的非静态属性都必须是private

在运行这个应用程序时,如咱们所见,page会展现当前的日期和时间,还有一些额外的连接。currentTime属性就是这些值的来源;很快咱们会明白这个值是如何被模板引用到的,那样它就能够从page和输出那里获取到了。

Tapestry老是会把page对应到一个模板;它们缺乏了对方都是没有用的。事实上,一个page中的component也是以一样的方式被对待的(此外component并不老是有对应的模板)。

你回经常听到模型-视图-控制器模式MVC)。模板就是MVC中视图。而做为模型的page会暴露出能够在模板中被引用到的JavaBean

让咱们来看看component模板是如何在Java类上构建出完整的用户界面的。

Component模板

Tapestrypage是一个POJO Java 类同一个Tapestry component模板的组合。模板会有跟Java类相同的名称,不事后缀名会是.tml。由于这里的Java类是com.example.tutorial.pages.Index,因此模板文件就会被放置在src/main/resource/com/example/tutorail/pages/Index.tml。最终,Java类和component 模板文件都会被存储在用于部署的WAR文件的同一个目录之中。

Tapestrycomponent模板是形式良好的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 XWindows)上仍然有效,不过在其它的(Linux和大多数其它的)上面就不行了。这可真使人烦心,由于在Windows上面作开发,而部署到LinuxSolaris上面是很常见的事情,因此这一处仍是当心为是。

Tapestry中,对于诸如Index.tml这样的component模板,其目标就是尽量想一个普通的,静态的HTML文件。(这里的静态static的意思是对比一个动态生成的Tapestry page,不用修改的意思)。

事实上,在许多状况下咱们所指望的是,模板开始是一个静态的HTML文件,有web开发者建立出来,而后被组装做为一个动态的Tapestry page

TapestryXML命名空间里面隐藏了非标准的元素和属性。按照约定,前缀“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,它们不作修改就会被向下传递给玩野浏览器。模板的动态部分由componentexpansion来呈现。

模板中的扩展(expansion

让咱们从exansion开始。Expansion是在渲染页面时包含一些动态输出的简便方式。Expansion默认会引用pageJavaBean中的属性。

 

<p>The current time is: ${currentTime}</p>

 

大括弧中的值是一个属性表达式。Tapestry使用其本身的属性表达式语言,富有表现力,快速,且类型安全。

Tapestry并无使用反射来实现属性表达式。

更高级的属性表达式能够横向引用多个属性(例如:user.address.city),或者甚至调用公共方法。这里的expansion简单的读取了pagecurrentTime属性。

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 pagecomponent均可以拥有其本身的消息目录(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以两种方式在component模板中表示:

做为一普通的元素,不过带有一个t:type属性,用来定义component的类型。

做为Tapestry命名空间中的一个元素,这种状况下元素的名称决定其类型。

这里咱们使用了一个<html>元素来表示应用程序的Layout(布局)component

 

<html t:type="layout" ...> 

  ...

</html>

 

而对于 EventLink component,咱们使用了Tapestry 命名空间中的一个元素:

 

<t:eventlink page="Index">refresh page</t:eventlink>

 

选哪中形式就是一种选择而已。在多数状况下,二者几乎是同样的。

跟其余地方同样,大小写是无关的。这里的类型(“layout”和“eventlink”)都是用的小写;实际的类名称是 Layout 和 EventLinkTapestry会进一步将核心库的component同这个应用程序定义的component“混淆”;如此类型“layout”会被映射到应用程序的componentcom.example.tutorial.components.Layout,而“eventlink”会被映射到Tapestry内置的org.apache.tapestry5.corelib.components.EventLink类。

Tapestrycomponent是使用参数来配置的;对于每一个component,都有一堆参数,每个都带有一个特殊的类型和目的。某些参数是必需的,其它是可选的。元素的属性被用来将参数绑定到特定的字面值,或者是page的属性。Tapestry在此处是很灵活的;你老是可以将属性放到Tapestry的命名空间中(使用“t:”前缀),不过在大多数状况下,不必这么作。

 

<html t:type="layout" title="tutorial1 Index"

      p:sidebarTitle="Framework Version" ...

 

这个将Layout component的两个参数,titlesidebarTitle对应绑定到了字面值“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 componentpage参数被绑定到了字面值“Index”(就是这个page的名称)。其或被渲染成一个从新渲染这个pageURL,解释了当前时间是如何被更新的。你也能够建立到应用程序中其它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猜谜游戏

相关文章
相关标签/搜索