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

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

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

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

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

l 非代码资源(包括Tapestry页面和组件模板)放在src/main/resources和src/test/resources下面

让我们来看看Maven根据原型创建了写什么,先从web.xml配置文件开始:

src/main/webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE web-app

        PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

        "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江北被用于任何没有在上下文名称后面指定额外路径的请求。

Index Java 类

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类上构建出完整的用户界面的。

Component模板

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来呈现。

模板中的扩展(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以两种方式在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猜谜游戏

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Ryan Miao

springmvc学习笔记--json--返回json的日期格式问题

(一)输出json数据 springmvc中使用jackson-mapper-asl即可进行json输出,在配置上有几点: 1.使用mvc:annotation...

491100
来自专栏Golang语言社区

linux 内核同步机制使用

Linux 内核中的同步机制:原子操作、信号量、读写信号量、自旋锁的API、大内核锁、读写锁、大读者锁、RCU和顺序锁。 1、介绍 在现代操作系统里,同一时间...

43750
来自专栏玩转JavaEE

Spring Cloud中Hystrix的请求合并

在微服务架构中,我们将一个项目拆分成很多个独立的模块,这些独立的模块通过远程调用来互相配合工作,但是,在高并发情况下,通信次数的增加会导致总的通信时间增加,同时...

39670
来自专栏爱撒谎的男孩

Springmvc响应Ajax请求(@ResponseBody)

3.4K80
来自专栏xingoo, 一个梦想做发明家的程序员

Elasticsearch安装

在启动或者安装ES之前,需要先下载JDK 1.7以上的版本,对于2.0来说,要求JDK1.8以上。 检查JDK的版本 使用命令: java -versio...

25860
来自专栏平凡文摘

面试问烂的 Spring AOP 原理、SpringMVC 过程

Spring AOP ,SpringMVC ,这两个应该是国内面试必问题,网上有很多答案,其实背背就可以。但今天笔者带大家一起深入浅出源码,看看他的原理。以期让...

16120
来自专栏程序员互动联盟

linux设备驱动第五篇:驱动中的并发与竟态

综述 在上一篇介绍了linux驱动的调试方法,这一篇介绍一下在驱动编程中会遇到的并发和竟态以及如何处理并发和竞争。 首先什么是并发与竟态呢?并发(concurr...

368100
来自专栏JavaEE

Thymeleaf的使用前言:一、thymeleaf简介:二、thymeleaf标准方言:三、thymeleaf与springboot集成案例:总结:

最近听说thymeleaf好像也挺流行的,还说是spring官方推荐使用,那thymeleaf究竟是什么呢?spring为什么推荐用它呢?怎么用呢?本文将为你揭...

14720
来自专栏小勇DW3

Spring的原理性总结

Bean的生命过程可以借鉴Servlet的生命过程,了解其生命过程对于不管是思想还是以后的使用都很有帮助;

2.6K50
来自专栏pangguoming

Spring Boot Maven Plugin打包异常及三种解决方法:Unable to find main class

63620

扫码关注云+社区

领取腾讯云代金券