了解 Tapestry,第 2 部分

Tapestry 框架允许 Java™ 和 Web 开发人员开发动态的、轻量级的和富于响应性的基于 servlet 的 Web 应用程序。在本文中,Brett McLaughlin 继续介绍 Tapestry,即如何规划 Tapestry 应用程序的开发,创建有用的、健壮的 Tapestry 组件。

这个简短系列的第一篇文章 是 Tapestry 的介绍 —— Tapestry 是一个构建 Web 应用程序的轻量级框架。如果曾经阅读过那篇文章,应当对 Tapestry 的工作方式有了基本的理解,并了解了如何把它的核心 API 组合在一起。您可能还不敢确定如何从头开始实际开发 Tapestry 应用程序,但是在这篇文章中,我将解决这个问题。

我要从一种简单的应用程序规划方式开始,在使用 Tapestry 时,这个方式特别重要。然后,我将讨论 HTML 原型在 Tapestry 开发中的角色,并解释在编写 Tapestry 组件之前需要具备的元素。最后,我将介绍如何开发 Tapestry 组件,并把它们与 HTML 页面链接起来。您还会学到一些技巧,以确保您的规划能够适应使用应用程序的人,这是成功开发的关键,并介绍如何为了重用而规划和开发 Tapestry 组件。在开始之前请参阅 参考资料 一节下载 Tapestry。

规划应用程序

如果您属于某类开发人员,您可能会憎恨规划 这个词,认为应该把时间花在做些实际的事情上!但是,规划是开始构建 Tapestry 应用程序(或者其他类型的应用程序)的最好途径,所以我将从介绍如何尽可能没有痛苦地进行规划开始。

Tapestry 框架使用实际的 HTML 页面,把它们当作模板,并把这些模板与 Tapestry 组件链接在一起。然后通过部署描述符把所有这些捆绑在一起,形成耦合紧密且相当复杂的文件集。Tapestry 应用程序有以下的典型组件:

  • HTML 页面(被 Tapestry 当作模板)
  • Tapestry 类
  • Java bean 和工具类
  • servlet 部署描述符 (web.xml)
  • Tapestry 的应用程序描述符(app.application)

如果一个猛子扎进去开始开发应用程序代码,眼前很快就会充满杂乱的注释、难以寻找的 bug,以及有时已更新有时没更新的模板。规划是真正有效利用 Tapestry 的惟一途径,所以请尝试尽可能无痛苦地做规划的这个三要点方式。





回页首


从问题开始

规划的第一部分是询问一个简单但是非常重要的问题:这个应用程序要做什么? 虽然看起来可能微不足道 —— 甚至陈腐 —— 但是,这是对应用程序所能提出的最重要的问题。而且,令人惊讶的是,它又是最经常被忽略的。下面是在开发过程开始时一些比较常见的问题的列表(听起来挺熟?):

践踏客户和规范!

不,不是真的。即使您憎恨规划,如果没有与应用程序的利益相关者(stakeholder)沟通,那么就要准备失败了。不论代码有多漂亮,都必须按照客户的期望工作 —— 哪怕您对客户的期望不以为然,也要如此。所以,现在请保留对规划的判断(即,它有多讨厌),并尝试我在这里演示的方式。每次都会形成更好的应用程序!

  • 要使用什么技术?
  • 什么样的客户机(Web 浏览器、移动电话、PDA)要访问应用程序?
  • 应用程序要在哪个平台上运行(Windows、Linux、Mac OS X、Sun Solaris,等等)?
  • 什么时候必须完成?

这些都是有用的问题,但是如果应用程序实际做的工作不是利益相关者所期望的,那么所有这些都无关紧要。

把这应用于 Tapestry,如果不知道应用程序的基本目的,就不会写出好的 Tapestry 代码 —— 不论代码的技术有多漂亮。Tapestry 代码用于两个基本目的:

  • 与数据源交互并实现业务逻辑。
  • 向表示组件(像 HTML 模板)提供数据。

因为编写 Tapestry 代码不是为了做炫耀的显示或视觉效果,所有的 Tapestry 代码都应当处理应用程序的核心任务。如果不知道核心任务是什么,那么就等着浪费大量时间开始一个空白屏幕,然后迷惑不解地想知道接下来要做什么吧。





回页首


描绘出业务过程

一旦理解了应用程序的基本目的,就可以开始列出实现这个过程所包含的全部具体业务过程。业务过程与应用程序的目的不同。应用程序的目的 通常是简洁的、高层的描述,概括应用程序做的每件事。业务过程 则是小得多的工作单位,通常以各种方式和其他业务过程一起重用,共同实现特定的任务。

例如,假设要编写一个应用程序,管理在 BurgerDome 的厨房。基本目的可能只是 “让客户得到想要的,按单烹饪”。但是这个目的自然有许多独立的业务过程;比如下面这些:

  • 下新单
  • 做一个汉堡
  • 向汉堡加蔬菜和调料
  • 将汉堡打包,递送给客户

当然,可能会发现这些过程还可以进一步细分:

  • 做汉堡
    • 做汉堡馅
    • 加热饼胚
    • 烤饼胚
  • 给汉堡加材料
    • 加蔬菜
    • 加调料
    • 加调味品
    • 加芝士

这些过程可能有点傻,但是很快就会看到即使一个相对简单的应用程序也会有 50 或 100 个不同的业务过程,可以用它们或者把它们与其他过程组合在一起,实现一个基本目的(在这个示例中,是制作客户点的汉堡)。

把业务过程转换成组件

关于 Tapestry 中的业务过程,有趣的是,实际上只有很少的被绑定到 Tapestry 类。例如,BurgerDome 应用程序的大多数任务根本没有绑定到显示,而且构建一个 Tapestry 组件去给汉堡加芥茉没有太大意义。在这个应用程序的规划阶段,描述出所有过程,会有助于确定实际需要使用 Tapestry 创建什么业务组件,以及哪些组件要传递给其他类型的组件。例如,可能创建了一个 Tapestry 组件,接受汉堡订单做为输入,但是可以把它在添加调味品的请求中传递给另一个非 Tapestry 组件。

在规划阶段的末尾,应当已经描绘出一套处理业务的每个具体细节的业务对象。除此之外,应当有一套 Tapestry 对象,把这些业务对象组织成有意义的工作单元。在清单 1 中可以看到这个编程模型的实际作用:


清单 1. 简单的订单类


package com.burgerdome.display;

import org.apache.tapestry.annotations.Persist;
import org.apache.tapestry.html.BasePage;

import com.burgerdome.order.*;

public abstract class Order extends BasePage
{
    private Order order;

    public void placeOrder(int burgerType, int doneness)
    {
        Burger burger = new Burger(burgerType, doneness);
        Order order = new Order();
        order.addBurger(burger);
        setOrder(order);

        OrderQueue queue = OrderQueue.getInstance();
        queue.add(order);
    }

    public void cancelOrder()
    {
        OrderQueue queue = OrderQueue.getInstance();
        queue.remove(order);
    }

    protected void setOrder(Order order) {
        this.order = order;
        order.setID(OrderUtils.newOrderID());
    }

    protected Order getOrder() {
        return order;
    }
}

从清单 1 可以看到,Tapestry 组件用 BurgerOrder 类与屏幕交互(假设允许客户下订单),这两个类都是 BurgerDome 业务组件库的组成部分。注意,BurgerOrder不是 Tapestry 类,而且实际上根本不知道 Tapestry。另外,OrderQueue 类既不是 Tapestry 类也不是 业务组件,它处理许多业务方面:接受订单并用有用的方式组织它们。

这里的要点是,如果没有仔细的规划,这些过程就不会成为一个整体。关于确定哪个类特定于 Tapestry、哪个特定于业务,以及它们如何交互,所有这些都最好是在代码编辑器中面对一个空白类之前做完。





回页首


创建导航图表

一旦理解了拥有什么 Tapestry 组件,以及基本业务组件如何协作,那么开发应用程序的导航就相对容易了。这个时候请记住,可能还不需要编写 HTML 页面或者四处乱放 href。用两页纸画好页面,并用箭头把页面连接起来,应当就够了。或者用另一种方法,即采用简单的流程图,图上的每个方形或矩形代表一个屏幕,箭头代表用户在应用程序中可能经历的各种 “路径”。

不论如何做,规划应用程序的导航实际上是一个简单但却会大大有利于开发过程的实践。一旦开始手动构建 HTML 页面(下一节将详细讨论),就不必再花时间考虑在页面上有什么链接、应当链接到哪,因为所有这些都已经规划好了。

描绘导航的一个重要的附带作用就是可以迅速地发现 “孤儿页面”。孤儿页面是这样一些页面,它们要么是没有链接到其他页面,要么是没有应该那么多的页面链接到它们。这些页面代表的特性是,用户需要与之交互,但是如果不纠正导航设置,这些特性就可能无法(或者容易地)访问到。在应用程序开发过程中,由于没有正确地把页面连接到其他页面,所以孤儿页面很常见。在开始编码之前规划应用程序的导航,是尽早隔离并纠正问题的好方式,这会形成更平滑的开发周期。

反复规划

关于规划,需要知道的最后一件事就是,规划不可能完美,也不应当一成不变。虽然规划很重要、很有用,但是肯定不可能考虑好每件事,甚至已经做了的也会改变。不论花多少时间考虑应用程序的目的、规划它的导航,都会遗忘一些事情。而且,即使什么也不忘记,也必须面对后来的特性请求或者在最想不到的地方出现 bug 或者生活本身的不可见因素;所以请保持灵活。

形成健壮的计划,然后愿意根据需要调整它,那么在应用程序开发中就占了先机 —— 特别是在使用 Tapestry 开发应用程序时。





回页首


编写 HTML 页面

应用程序规划好后,就可以编写 HTML 页面了。虽然从严格意义上讲,这不是真正的 “编码”,而且可能被当作可以留在最后再做的事情,但是应当一直从编写 HTML 代码开始应用程序的开发。

什么时候开发业务对象

编写业务对象的时机,对于具体的项目和公司是各不同的。对于许多项目来说,将使用现有的业务对象,所以根本不必编写代码(或者可能只是编写少量代码)。在某些情况下,编写的业务对象会用于多个应用程序,所以最好在编写具体的应用程序代码之前开发这些对象。而在其他情况下,可以在编写应用程序的剩余部分时开发业务对象。简而言之,开发业务对象的正确时机主要依个人偏好和项目要求而定。

从 HTML 开始的最大理由是:客户、最终用户、营销团队、经理以及 alpha 和 beta 测试人员会看到这些页面。虽然可以给 Java 类添加 main() 方法,并在命令行测试它们,但是多数用户会发现 Web 浏览器最适合测试 Web(特别是 Tapestry)应用程序。

更重要的是,Tapestry 用 HTML 文件作为它的页面模板,所以如果没有基本的 HTML 页面,开发 Tapestry 组件的压力会很大。在许多情况下,页面设计实际上会指明您为 Tapestry 组件所做的决策。

这一节介绍编写应用程序 HTML 的基础。





回页首


从原型开始

在开始拼凑出成百行的 CSS 样式表和复杂的流动布局之前,请认识到最好的应用程序原型是简单的,有时甚至是粗陋的。最好是从一个非常基本的页面开始,就像清单 2 所示的那样:


清单 2. 销售报表的原型


<html>
 <head><title>Sales Report Prototype</title></head>
 <body>
  <h1>Prototype Sales Report</h1>
  <table>
   <tr><th>Total Sold</th><td>1012</td></tr>
   <tr><th>Sales Price</th><td>$29.95</td></tr>
   <tr><th>Manufacturing Cost</th><td>$8.22</td></tr>
  </table>
  <h2>Net Profit: $167718.76</h2>
  <form method="GET">
   <input value="Get Updated Sales" type="button" />
  </form>
 </body>
</html>

图 1 显示了这个页面出现在 Web 浏览器中的效果:


图 1. Web 页面原型
好的原型非常简单,而且标记清楚

这看起来不是很像,但这就是关键!松散的、简单的页面,可以容易地移动或修改事物。添加表格行或把标题移动到顶部,做起来会很简单,不会弄乱精心调整的布局或颜色方案。另一方面,请想像一下,如果花了几个小时处理样式表,才能让标题就位,让打印效果和颜色方案满意,然后进入会议室,却听到下面这些评论,您会有什么感受:

“这个菜单项需要再向下一点”

“我讨厌橙色的阴影;公司的 logo 用海蓝色。”

“我们不能用 serif 字体么?”

关于这类评论,最糟的是,它们并不是您想得到的应用程序资金提供者、销售人员或用户的评论。但是,需要指出的重要的一点是,这些评论没有一个与页面应当做什么、它要传递的基本信息是什么有关。在原型阶段保持事物简单,可以让应用程序的利益相关者对于应用程序的功能拥有基本的认识,而不会把大量时间浪费在颜色和设计这些可能会改变的问题上。如果有人抱怨原型中事物看起来的样子,您可以向他们保证最后完成的 Tapestry 应用程序看起来会很棒!

这是一项未完过程

就像我在清单 1 和图 1 中所做的那样,请一直认真地把原型标记为原型。在 HTML 的标题上(在 title 元素中)和实际的页面中都要放上单词 “prototype”(h1h2 元素通常可以胜任这个工作)。清楚地标记原型过程的每一部分的好处很明显:

  • 在向销售人员和最终用户介绍原型时,他们不会认为自己正在看的是完成的产品。这会挡住那些让大多数开发人员发疯的评论,例如 “为什么它看起来这么差?” 和 “我们能不能让表格的文本用紫红色?”

  • 如果有些勤奋的销售经理把您的工作展示给总经理,他们也会 认识到工作还在进行当中,而不会闯入您的办公室,质问您为什么付给您那么多,您却只拿出这么可怕的 Web 页面。

  • 在开始开发 Tapestry 组件时,这些 “prototype” 标题可以提醒您页面什么时候已经真正完成而什么时候仍然在进行当中。请确保在页面编码完成,可以部署和测试时,删除 “prototype” 标题是所做的最后一件事。

虽然原型法一直是个好的开发实践,但是 Tapestry 还进一步让它成为特别有益的 实践。在某些开发环境下,原型模板在进入实际开发时,有时会失效,但是使用 Tapestry,可以方便地用原型作为开发应用程序的模板。例如,在使用独立的 Java servlet 时,最后必须抛开原型模板,把 HTML 拷贝粘贴到 out.println() 语句或 JSP 标记中。但是使用 Tapestry,可以把原型属性有效地应用到开发工作中。如果要证明在 HTML 模型上花的时间没白花,Tapestry 可以证明!





回页首


使用真实数据

基本页面准备好之后,需要用真实数据填充模型。对于多数开发人员来说,很容易用简单的金额(例如 $99.95)代表销售额,后面再用一些傻乎乎的文本值,例如 “foo” 或者一些长长的拉丁字符串(对于我和其他要查看模型的人来说,这种做法毫无意义),但是请不要这么做!由于两个原因,使用不真实的占位符是个坏主意:

  • “假” 数据不能代表真实数据在页面上会占据的空间。

  • 页面包含一两个单词时的样子,与包含多行文本时的样子,会有显著不同。同样,只有二、三位数字的数值与有五、六位数字的值看起来也不同。

我不是在建议您需要去做无数的调研,然后在页面中使用正确的、实时的数据。只需要对于数据最后在页面上会是什么样子有良好的认识就可以。例如,如果销售价格的范围预计是在 $50.00 和 $5,000.00 之间,那么这对开发真实的原型就是足够的信息。对于文本长度来说,也是一样,不过,可能需要更具体一些的范围;如果文本要在 1,500 和 3,000 字之间,可以规划得很好。如果文本会在 1,500 和 30,000 字之间,那么就很难编写出能容纳两端范围之间的标记(所以也应当让利益相关者知道这一点)。

对于数据的范围和类型有了主意之后,可以开发能够显示范围两端的原型;例如,对一个页面,使用 $50.00 的值,另一个页面用 $5,000.00 的值。这样可以保证所有可能的值都会适合所分配的空间,按照预期形式换行,格式化也正确。对于文本、标题、表单字段等,也是一样的。在页面上出现的任何包含数据的内容,都应当用真实数据而不是一堆 “程序员” 值进行测试。





回页首


添加结构元素

现在,可以添加 JavaScript、图片和活动导航了。在许多 Web 开发框架下,可能就是在这里抛弃原型,开始把标记转换成 servlet、JSP 或视图组件的。但是,使用 Tapestry,可能已经得到了有用的模板,只需要添加一些 “分隔和修饰”,好让原型更适合使用。

可以从为页面添加 CSS 样式表开始,与 divspan 元素一起,区分页面不同区域并设置区域的样式。现在开始要认真了,可能还需要改变一些页面结构,例如从占位符列表转换成表格,或者反过来。所有这些变化,都会给页面添加新的结构层和样式,使它们更加可用于应用程序中。

如果想用 JavaScript 给页面添加动态值或处理图片交换,现在可以把这个代码加到页面里了。也可以给页面添加图片。不论做什么,请记住,正在处理的页面,会实际地 由 Tapestry 用来驱动应用程序,所以正在执行的是有用的工作,而不再是浪费时间在完善模型上。

添加结构到模板

在这个阶段所做的全部工作是创建更好的模板。而且,对于 divspan 元素,实际上是在准备 Tapestry 交互。在清单 3 中可以看出这点,清单 3 是清单 2 所示的原型的 HTML,只是新添加了一些 divspans


清单 3. 添加销售报表的结构


<html>
 <head><title>Sales Report Prototype</title></head>
 <body>
  <h1>Prototype Sales Report</h1>
  <div id="sales">
   <table>
    <tr><th>Total Sold</th><td><span id="total-sold">1012</span></td></tr>
    <tr><th>Sales Price</th><td>___FCKpd___2lt;span id="board-cost">29.95</span></td></tr>
    <tr><th>Manufacturing Cost</th><td>___FCKpd___2lt;span id="man-cost">8.22</span></td></tr>
   </table>
   <h2>Net Profit: ___FCKpd___2lt;span id="net-profit">167718.76</span></h2>
   <form method="GET">
    <input value="Get Updated Sales" type="button" />
   </form>
  </div>
 </body>
</html>

虽然变化不大,但是可以看出页面中的每个值现在有了一个 span 设置,还有一个 ID 标记。所以,页面可以容易地转化成 Tapestry 组件,下一节就会看到。页面中的数据是真实数据(基于项目的规范),结构也准备好了,可以转入 Tapestry 应用程序了。





回页首


导航性注释

惟一剩下要做的就是创建页面间的导航路径。现在,可以用普通的 HTML a 元素加 href 属性做这件事,但是在创建最终应用程序时,这些链接可能必须变化。如果在 Tapestry 中想避免大多数页面间的直接链接,可以访问 “即时的” Tapestry 页面,它只使用 HTML 文件作为模板。

即使有这个秘诀,也要花相当的时间构建一套页面间的链接。理解这些链接将去向哪里,它们如何呈现给用户,是构建 HTML 框架的重要部分。

结构元素、图片和导航链接都设置好之后,几乎就可以开始编写 Tapestry 组件了。在这之前,最好是让团队、经理、销售人员,可能还有 alpha 测试人员,把所有内容都运行一下。这可以确保在开始编码之前,应用程序的观感符合利益相关者的需要。





回页首


构建 Tapestry 组件

对于大多数开发人员,第一次使用 Tapestry 的最大惊讶就是几乎没什么事可做。如果在应用程序规划和 HTML 页面上的工作做得很好,那么编写 Tapestry 代码就变得非常简单。Tapestry 主要作为粘合剂,将应用程序的表示和驱动应用程序的逻辑粘合起来,所以实际上在 Tapestry 中上花在编写复杂代码的时间惊人地少,更多的时间花在把 HTML 页面连接到业务组件上。

实际上,这是 Tapestry 的一个真正亮点:替您铺好路。因为编写了标准化的 HTML,并添加了一些特定于 Tapestry 的元素,所以几乎没有影响 HTML 的 Tapestry 代码。更好的是,Tapestry 标记不会影响应用程序的显示,所以您的标记可以包含这些标记,您的设计什么也不包含。换句话说,在使用 Tapestry 和不使用它的页面之间,永远不会看出视觉上的差异。而且,业务逻辑也根本不会被 Tapestry 影响。惟一真正属于 Tapestry 的代码就是一套简单的类,用来把应用程序的所有片段连接起来。在使用其他许多框架时,从只有 servlet 或 JSP 的框架到诸如 Struts 或 Spring 之类更复杂的框架,通常要编写许多特定于框架的和与框架有关的代码。幸运的是,使用 Tapestry 时不需要这样。

处理业务逻辑

在开始编写 Tapestry 组件之前,还有最后一件必须要做的事:必须确保没有业务逻辑终止于 Tapestry 代码中。这意味着应当精心定义了(最好已经编写了)所有业务任务的类。如果没有做这个工作,就会被引诱着(通常是被迫)把一些逻辑放在 Tapestry 页面中。有时,这么做会是让应用程序运行起来的 “最快” 途径,但是在这类情况下,“最快” 通常是名不符实的,因为业务逻辑更改时,要花很多时间对应用程序做修改(如果因为订购鞋的方式或冰箱送货的方式变化,就必须修改驱动显示的代码,那么这可不是好的应用程序设计!)

所以,请在应用程序的表示(已经开发的 HTML 页面)、业务逻辑(已经准备好的 Java 类,通常在另一个 JVM 或服务器上)和把这些部分连接到一起的胶水(Tapestry 代码)之间保持清晰的界限。遵守这个简单的原则,会让 Tapestry 代码编写起来更快,因为只是调用业务对象并用调用的结果更新表示而已。





回页首


从对象开始

开始开发 Tapestry 组件时,可以对需要至少一段动态数据或需要与业务对象交互的 HTML 页面做一个列表。这个列表应当只有文件的名称和页面目的的简短描述,例如:

  • Home.html:应用程序主页。
  • Order.html:新订单的主订单页面。
  • Status.html:检查订单状态的页面。
  • Comments.html:留言页面。

对于列表中的每个页面,都要创建一个新的 Java 类,可以用与页面相同的名称作为类名。例如,清单 4 是驱动 Comments.HTML 页面的类的骨架代码:


清单 4. 检查订单状态的简单 Tapestry 类


package com.burgerdome.display;

import org.apache.tapestry.annotations.*;
import org.apache.tapestry.html.BasePage;

public abstract class Status extends BasePage {

  @Persist
  public abstract int getOrderNumber();
  public abstract void setOrderNumber(int orderNumber);

  // Methods go here
}

可以从这些行开始每个 Tapestry 类:类名与它交互的文件的名称(Status.java 用于 Status.html、Comments.java 用于 Comments.html,等等),确保类扩展自 org.apache.tapestry.html.BasePage 类。确保给 Tapestry 类提供了一个包;通常在同一包中找到所有页面最容易。还需要导入 BasePage,而且前进一步并导入 Tapestry 注释也是一个好主意;在开发的几乎每个 Tapestry 组件中都会使用它们。

最后,前进一步,设置可能需要的持久变量;清单 4 中的示例保存一个订单号,该订单号用于在应用程序的业务对象区查询订单。请了解 @Persist 并不代表 Tapestry 要在数据库或其他永久存储中持久化或保存变量;它只表明变量在重复调用对象实例期间一直可用。这意味着可以允许用户只输入值(在这个示例中代表订单)一次,然后反复使用这个值,而不需要用户每次返回状态页面都输入这个值。还请注意,没有为持久变量声明类型;只是提供了 “getter” 和 “setter” 方法,而 Tapestry 负责剩下的处理。类本身被标记成抽象的,这允许 Tapestry 负责设置类的实例,并把实例挂接到 Tapestry 引擎。

清单 4 中的简单示例可以充当所有 Tapestry 对象的起点。只要修改名称和任何需要的持久变量,让页面对象投入使用的工作就完成了一半(有时甚至更多)。





回页首


添加操作

下面考虑没有绑定到页面上的简单值的操作。例如,在状态页面中,可能让用户输入订单号,然后让另一个按钮或链接向用户提供他们的状态。第一个操作被紧密地绑定到清单 4 所示的 setOrderNumber() 方法,第二个操作则需要查询订单号。清单 5 展示了处理这个任务的简单代码:


清单 5. 添加订单处理


package com.burgerdome.display;

import org.apache.tapestry.annotations.*;
import org.apache.tapestry.html.BasePage;

import com.burgerdome.order.*;

public abstract class Status extends BasePage {

  public abstract Order getOrder();
  public abstract void setOrder(order);

  @Persist
  public abstract int getOrderNumber();
  public abstract void setOrderNumber(int orderNumber);

  public void getStatus() {
    OrderQueue queue = OrderQueue.getInstance();
    Order order = getOrder(getOrderNumber());
    setOrder(order);
  }
}

您会注意到这个代码中的几个新部分。首先,导入了一些业务对象;在这个示例中,这些对象在 com.burgerdome.order 包中。其次,我添加了两个新方法:getOrder()setOrder()。这两个方法被标记为抽象的,这样 Tapestry 会把它们实现为简单的 “getter” 和 “setter” 方法,并为这个类创建类型为 Order 的新变量。除非确实有好的理由不这么做,否则最好是让 Tapestry 替您管理这些变量。

还请注意这两个新方法被放在 @Persist 注释上面。这意味着订单在请求或会话间不会被持久存储。因为订单在变化,所以在每次请求的时候查询它并检查它的状态会更容易。还请记住,因为业务对象通常在独立的 JVM 中运行,订单可能在另一个 JVM 中在变化,所以本地保留的对象拷贝可能会过时。

一般来说,只持久存储对客户来说不改变的条目(比如订单号本身,它在请求之间不会改变)或者不由业务对象使用的条目。例如,可能有一个分配给客户的表编号,业务对象不会在上面操作,所以让它持久存储在 Tapestry 中是可以接受的。

这个类中的最后一件事是添加了 getStatus() 方法,用于把订单号(已持久存储的)连接到订单,该订单是在每次请求时都要查询的。用户查找到他们的订单之后,就可以容易地通过订单的方法访问订单了;可以调用 order.getRemainingCookTime()order.change() 方法,还可以在需要的时候让 Tapestry 把这些请求发送回业务层。

在您自己的应用程序中,这是惟一真正需要编写许多特定于 Tapestry 的代码的地方:当与显示有关的事件发生时,必须修改业务对象。用这种方式,Tapestry 把用户的动作连接到后端代码,后端代码对这些动作作出响应。





回页首


回顾 HTML 链接

Tapestry 代码就绪之后,就需要回到 HTML,把 HTML 与刚才编写的代码连接起来。实际上,要做的全部工作只是找到页面中的所有 aspan 元素。对于 a 元素,先找出哪个链接到外部页面 —— 在其他站点上、不是应用程序的一部分的页面 —— 并 “丢弃” 它们(换句话说,不用考虑它们)。剩下的应当具有像这样的链接:


<a href="Home.html">Return to main screen</a>

添加另一个属性,叫做 “jwcid”,它的值为 “@PageLink”。这让 Tapestry 知道正在创建到其他 Tapestry 页面的链接。现在的链接看起来像这样:


<a href="Home.html" jwcid="@PageLink">Return to main screen</a>

然后,把 href 属性的名称改成 “page”。page 属性让 Tapestry 知道要连接到其他哪个由 Tapestry 控制的页面(这就是为什么可以忽略外部链接的原因:它们在那呆着就很好)。然后,删除 “.html” 扩展名。现在的链接看起来像这样:


<a page="Home" jwcid="@PageLink">Return to main screen</a>

最后,a 元素要求 href 属性,所以把它添加回去,但是值为 “#”。这告诉 HTML 把它连接回同一页面,然后 Tapestry 处理实际的链接。最后的链接看起来像这样:


<a page="Home" jwcid="@PageLink" href="#">Return to main screen</a>

用这种方式把每个 HTML 页面中的每个链接进行转换,然后应用程序的导航会在 Tapestry 和动态页面之间开始工作,而不是在静态的 HTML 模板之间。





回页首


添加动态数据

现在需要把假日期更新成即时值。通过 @Insert 注释做这件事最容易。作为示例,请看清单 6,它把第一个 span 标记转换成使用来自与这个页面相关的 Tapestry 类的值:


清单 6. 添加真实数据到 HTML 模板


<html>
 <head><title>Sales Report Prototype</title></head>
 <body>
  <h1>Prototype Sales Report</h1>
  <div id="sales">
   <table>
    <tr><th>Total Sold</th><td><span jwcid="@Insert" value="ognl:totalSales" 
      id="total-sold">1012</span></td></tr>
    <tr><th>Sales Price</th><td>___FCKpd___9lt;span id="board-cost">29.95</span></td></tr>
    <tr><th>Manufacturing Cost</th><td>___FCKpd___9lt;span id="man-cost">8.22</span></td></tr>
   </table>
   <h2>Net Profit: ___FCKpd___9lt;span id="net-profit">167718.76</span></h2>
   <form method="GET">
    <input value="Get Updated Sales" type="button" />
   </form>
  </div>
 </body>
</html>

@Insert 告诉 Tapestry 插入数据,而 value 属性则用来指定插入什么数据。在这个示例中,“ognl” 是 Tapestry 页面可以使用的库,用它可以调用 Tapestry 页面;“totalSales” 是页面对象中的变量名称。添加的这段代码的结果是页面显示时有了真实数据,而不是 1012 这个假值。

还需要处理所有的页面,做类似的修改,这样所有的假数据就被真实数据替换了。而且,因为花了时间来确保示例数据的长度是真实长度,所以在真实数据插入页面时,不会造成任何显示问题。





回页首


一点配置……

所有后台工作完成之后,再与几个配置文件连接起来就是小事情了。由于使用相同的名称,页面和类之间已经创建了隐式的映射。现在,需要通过显式化,让 Tapestry 引擎知道这个隐式映射。请在 servlet 上下文的 WEB-INF/ 目录中创建一个新文件,叫做 app.application。然后添加一个叫做 “org.apache.tapestry.page-class-packages” 的新键,并用 Tapestry 类所在的包的名称作为键值。清单 7 展示了一个示例 app.application 文件:


清单 7. app.application 文件


<?xml version="1.0"?>

<!DOCTYPE application PUBLIC
  "-//Apache Software Foundation//Tapestry Specification 4.0//EN"
  "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd">

<application>
  <meta key="org.apache.tapestry.page-class-packages" 
    value="tutorials.directlink.pages"/>
</application>

由于已经做了很多工作,所以这一步很简单。另外,Tapestry 框架已经设置好,所以这也很容易;简单的文件和类名称已经准备好,所以这只不过是连接两套组件而已。

还需要用标准的 Web.xml 部署描述符让 servlet 引擎知道,应用程序也在 WEB-INF/ 中。清单 8 展示了需要的内容:负责核心 Tapestry servlet 的 servlet 入口,以及这个 servlet 的 URL 映射:


清单 8. 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>BurgerDome Ordering System</display-name>

  <servlet>

    <servlet-name>app</servlet-name>

    <servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class>

    <load-on-startup>0</load-on-startup>

  </servlet>

  <servlet-mapping>

    <servlet-name>app</servlet-name>

    <url-pattern>/app</url-pattern>

  </servlet-mapping>

</Web-app>

现在已经有了 HTML 页面、Tapestry 组件、业务对象,以及把所有这些部分连接在一起的描述符。应用程序可以运行了!





回页首


结束语

在这篇文章中,我从 Tapestry 的初始概述(来自 了解 Tapestry,第 1 部分)转移到介绍如何用 Tapestry 框架实际地开发应用程序。我没有强调许多代码,而是集中在使用 Tapestry 开发应用程序的过程以及如何做好上面。虽然多数 Java 程序员可以容易地学会一个新 API,但是许多人更困难的是弄清楚如何最好地使用这个 API。我希望您从这篇文章懂得的是 Tapestry 开发中规划的重要性,以及良好的规划如何会有利于最终的实现。我强调的 Tapestry 开发过程的另一个好方面是重用。通过在编写组件之前仔细考虑,最终会得到可重用的组件工具箱,而不必编写不能共享组件的许多不同的应用程序。

虽然这篇文章的主题是 Tapestry,但是我讨论的许多想法可以应用于任何编程框架,特别是基于 Web 的框架。您会发现,大多数好的应用程序都涉及到精心的规划,为了避免不断的重新设计,不能草草规划。如果花时间仔细规划,然后设计原型,那么最后差不多总会得到更精致的、响应更好的应用程序,也会极大地提高客户和最终用户对最终产品满意度。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值