欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

tapestry 初学 转载

程序员文章站 2022-07-15 15:52:38
...
1、web.xml
一般来说,使用Tapestry需要在Tapestry中添加2项内容
A、    <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.demo</param-value>
    </context-param>
这个配置也比较重要,这个配置项告诉Tapestry去哪个package下面去找页面、组件、服务等东西。
在源代码中需要在此配置的package下面建立4个package,分别为:com.demo.pages, com.demo.components, com.demo.services, com.demo.mixins
现在流行约定高于配置,所以Tapestry默认要求使用这样4个package名称。
其中页面类放在pages目录下,组件放在components目录下,服务放在services目录下,mixins翻译起来比较困难(Tapestry的作者也说mixins是一个很tricky的概念,这是一种能让一个真的组件与一些其他特殊组件混合起来的东西)。
B、Tapestry的Filter,现在的Web框架基本上都使用Filter取代以前流行的servlet配置了。
    <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>
这里面需要注意的是filter-class中Tapestry的Package中,从5.0以后org.apache.tapestry变为org.apache.tapestry5了(多了一个5)。
这个filter-name是比较重要的一个名字,Tapestry没有一个象Spring那样的Xml配置文件,它的初始化配置工作在一个初始化类中完成,这个初始化类需要放在上面提到的services目录下,类名就是filter-name+Module.class,象我举的例子的话,就是AppModule.java


1、Start.tml和Start.java
Tapestry不需要在Web.xml中配置welcome页面,也是使用约定大于配置的方式,默认的起始页面就是在pages目录下的Start。Tapestry中的页面由两部分组成:一个Java类和一个相同名字的模板文件(类似于jsp,Tapestry中的后缀名为:tml)。其中Java类是必须的,就算是没有任何代码,也必须要生成一个空类。tml模板在大多数情况下也是需要的,少数情况比如返回二进制流的话,可以不需要。
tml模板文件可以放在webroot目录下,也可以和Java类一直放在src目录下。
2、页面的跳转(可以用于指定跳转页面的东西:) @InjectPage
在Tapestry中,页面的跳转和Struts之类的框架是不同的,Struts的做法是Action返回一个字符串,Struts在xml配置文件中进行搜索匹配,决定跳转的页面。而Tapestry没有类似的配置文件,它的做法是在当前页中引用跳转页面。比如:想从Start跳转到Register页面的话,需要在Start中注入Register页面。示例代码如下:
Class Start{
        @InjectPage
        private Register register;

        Object onSubmit() {
                return register;
        }

其中值得注意的是:onSubmit的修饰符并不是常见的public,而是缺省级别(或者说是package级别),这是Tapestry推荐的做法。第一,比public级别低,这样的话,页面类的事件处理函数没有被公开,不是其他所有的人都能随便调用。第二,比private级别高,这样如果测试需要的话,可以把测试类放在同一个package里,方便测试的进行。
在Tapestry的事件处理函数中可以有6种方式指定跳转的页面
a、什么也不返回,也就是返回类型是void,这样的话,页面不跳转,只是刷新当前页面
b、字符串。Tapestry会查找与该字符串对应的类
c、类。一般来说是一个Page类,这种方式比返回类名(字符串)要好,因为这样的话,在类重命名后,程序不会出错。
d、Page实例,
e、Link,
f、流(Stream),比如返回pdf或者Excel文件。
3、Tapstry页面中的表达式
类似于jsp中<%= 变量名%>这样的写法,Tapestry的语法是${变量名},其中变量名是需要在页面对应的Java类中有get方法的。

1、在Tapestry中,页面称之为页面模板(Page Template)。Tapestry的页面模板必须是一个Well Formed Xml,需要引入Tapestry的Xml命名空间才能使用Tapestry的组件。示例:<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">
2、在页面模板中可以使用表达式来获取变量的值。这种表达式在Tapestry中称之为:Expansion。
3、在前一篇文章中已经说过,一个页面模板必须有一个对应的Java类。在Tapestry中最基本的Expansion就是一个Java类的属性。比如:${userName}这就是一个在可以在页面模板中使用的Expansion,为了正确的显示用户名,必须在Java类中有一个对应的public String getUserName()这样一个方法。注意:是一个getter方法,而不必需是public String userName,实际上,如果没有userName这个属性,而只有getUserName这个方法,应用将能正确运行。
4、Expansion是不区分大小写的,也就是说${userName}和${UserName}是等价的。
5、使用Expansion,我们不仅可以显示Java类的属性,而且可以显示该属性的属性,比如Java类有一个日期型属性public Date getBirthday();那么,在页面模板中就可以这样使用:${birthday.time},相当于调用getBirthday().getTime()
6、也可以显示地调用非getter方法:比如:${birthday.hashCode()},Tapestry5.1开始,可以将属性作为函数调用的参数了。
7、可以使用?.来调用方法:比如:${birthday?.time},这样即使birthday是null,页面也不会报错。
8、Tapestry的Expansion是会编译时生成class文件的,而不是在运行时使用反射来取值的。Tapestry的作者认为这是Tapestry性能优于Struts2的可能(注意:Howard Lewis Ship也只是猜测)原因之一。
9、Tapestry的Expansion使用的几个说明:
        a、Expansion旁边的空白是会被忽略的
        b、整数和小数前面可以使用负号
        c、常数都是使用十进制的。
        d、字符串需要使用单引号
        e、可以使用..来表示一个范围,比如:1..5,表示1到5


在讲述页面之间传递值之前首先要介绍一个Tapestry的页面缓存。
Tapestry的一个经常被提及的就是页面的缓存,Wicket也有页面缓存,但是Wicket的页面是缓存在Session里的,缺省好像是只缓存5个页面。这一点让我对Wicket的印象打了折扣,首先,这种机制在高并发,也就是多人同时在线的时候,感觉页面缓存会占用大量资源。而Tapestry的页面缓存是在一个大池中。
页面缓存的优点就是提高效率,但是缺点也是比较明显的,那就是给开发实现带来了不方便。我觉得众人说的Tapestry的学习曲线比较陡,一部分原因就在这里。上一章讲过,在Tapestry中,页面的跳转是通过InjectPage实现的。Tapestry在页面跳转时,会从缓存池中选取一个相应页面的实例,渲染成html。关键在于Tapestry在把页面放到缓存池之前,会把页面实例里的值清空。这就是造成页面传值麻烦的原因。写段代码示例:
Class Register {
        private String name;
        public void setName(String name) {
                this.name = name;
            }
            public String getName() {
                return name;
            }
}
Class Start{
        @InjectPage
        private Register register;

        Object onSubmit() {
                register.setName("superman");
                return register;
        }
}

如果页面从Start跳转到Register,并且希望把superman这个值传递给Register这个页面,感觉上只要象上面这段代码一样简单设置一下就行了,但是实际上这样是无法在Register的页面上显示出superman这个名字的。因为Tapestry会在页面显示时候的时候,从缓存池中提取一个实例,这个实例在放入缓存池之前,name属性的值就被清空了。
下面是Tapestry页面之间传递值的几种方式:
2、Persist。最简单的方式是使用persist注解。
Class Register {
    @Persist
        private String name;
        public void setName(String name) {
                this.name = name;
            }
            public String getName() {
                return name;
            }
}
只要这样指定后,Tapestry会把name的值保存在Session中,以备下次使用。但是这样的缺点也很明显,首先是占用资源,另外这样的URL是不能作为书签的,因为参数值不体现在URL中,而是保存在Session中。
3、Page Active Context。这个方法比较好,但是需要一些代码来实现。主要是添加两个函数onActivate和onPassivate。
Class Register {

        private String name;
        public void setName(String name) {
                this.name = name;
            }
            public String getName() {
                return name;
            }
        void onActivate(String name) {
                this.name = name;
        }
        String onPassivate() {
                return name;
        }
}
在Start页面中,我们使用了InjectPage把Register注入到了类中,如果只是简单地返回Register,是不能正确显示name的值的,但是在Tapestry会在把Start页面中的Register放回缓存池,清空Register实例中的变量值之前,检查这个类是否但是现在我们实现了onActivate和onPassivate这两个方法,Tapestry会自动
4、SessionState
在Tapestry中有一种类型的对象存储在Session中,所以对所有页面都是可见的,但是又不需要对页面暴露Session。在Tapestry5以前,称之为ApplicationStateObject,但是这个称呼并不准确,因为这个对象不是Application级别,而只是Session级别,所以Tapestry5开始,改名为SessionStateObject。
使用方法也很简单,只需要在JavaClass中,给相应的属性添加一个@SessionState注解即可。
需要提醒注意的是,SessionState区分对象实例不依赖于实例的名称,而是根据实例的类型,比如:你在一个类中使用@SessionState注解了一个String name;而在另一个类中想再用@SessionState注解一个String password;这是不行的,Tapestry会把这两个当作同一个对象实例。
另一个需要注意的是,SessionState注解之后,Tapestry会马上会初始化生成一个该对象的实例。

1、Tapestry组件的写法
a、<t:textfield t:id="userName" t:value="jack"/>,这样的写法的优点是,看上去比较直观,与Struts等Web框架的一致。但是缺点就是,使用浏览器(或者美工)直接看页面的时候,浏览器无法正确显示这个组件。
b、<input type="text" t:type="textfield" t:id="userName" t:value="jack"/>这样写的话,浏览器就能正常显示一个文本输入框了。这也是Tapestry一直鼓吹的一大优点之一。
c、在Java类中使用注解来声明组件。这种方法我个人并不推荐,只是一种选择,并不直观也不方便。
d、Tapestry组件标签是不区分大小写的。
2、组件参数的前缀。从官方文档来看,参数的前缀还挺多的:
Prefix        Description
asset        The relative path to an asset file (which must exist).
block        The id of a block within the template.
component        The id of another component within the same template.
context        Context asset: path from context root.
literal        A literal string.
nullfieldstrategy        Used to locate a pre-defined NullFieldStrategy
message        Retrieves a value from the component's message catalog.
prop        A property expression to read or update.
translate        The name of a configured translator.
validate        A validator specification used to create some number of field validators.
var        Allows a render variable of the component to be read or updated.
但最最常用的只有2个:prop和literal
简单的说:prop表示这是一个变量名,literal表明这是一个常量
比如说select这个组件,它的基本写法是: <t:select t:id="color" model="literal:Red,Green,Blue" label="Colour:"/>
查看select的参考文档,model这个参数的缺省前缀是prop,所以如果直接写model="red"的话,Tapestry会认为red是一个变量名,会调用页面对应类的getRed()方法来获得一个列表。所以,此处如果想使用一个常量的话,需要显示指明literal前缀。而label的缺省前缀是literal,所以直接这样写就没有问题。
3、Label,显示一个标签,一般在需要多语言的环境下或者与textField配合使用。
<t:label t:for="userName">Label for the user name</t:label> Lable组件有一个t:for的属性,这个属性的值一般是一个textField的id。我们可以注意到Lable标签里写一段文字,这一段文字在页面部署运行后是不会显示的,写在这里,纯粹是给美工这样的静态页面浏览者看的。在实际运行中这一段文字会被替代。替代的内容就是t:for属性对应的TextField组件的t:id的值,比如:userName。当然Tapestry会自动把userName分成两个单词,而且把user的首字母大写。
如果根据t:id生成的lable不符合要求,可以有另一种选择:直接在textField中指定label:
<input type="text" t:type="textField" t:id="userName" t:label="Input User Name" t:value="jack"/>
4、PageLink,用于显示一个链接,点击后,跳转到指定页面
<a href="#" t:type="pageLink" t:page="register">Register</a>
5、ActionLink,用于显示一个链接,点击后,运行一个可以带有参数的指令。
<a href="#" t:type="actionLink" t:context="id" t:id="clickLink">
        ${user.id}
    </a>
  如果一个参数不够,比如,只用一个id不能唯一确定一个用户,必须结合部门id才能唯一确定用户的话,那么可以这样做:
  <a href="#" t:type="actionLink" t:context="userContext" t:id="clickLink">delete</a>
  相应的在java class中需要添加一个方法:getUserContext()
  public Object[] getUserContext() {
     return new Object[] {user.id, department.id};
  }
6、Loop
Loop并不是一个真正的Form控件,严格说起来是一种逻辑控制语句。语法如下:
<t:loop source="userList" value="user">
    <td>${user.name}</td>
</t:loop>
7、Radio & Radio
            <t:radiogroup t:id="type">
                <t:radio t:id="masterCard"/>
                <t:label for="masterCard"/>
                <t:radio t:id="visa"/>
                <t:label for="visa"/>
                <t:radio t:id="amex"/>
                <t:label for="amex"/>
                <t:radio t:id="dinersClub"/>
                <t:label for="dinersClub"/>
                <t:radio t:id="discover"/>
                <t:label for="discover"/>
            </t:radiogroup>

8、CheckBox
<t:checkbox t:id="showall" onclick="this.form.submit();"/> <t:label for="showall"/>
9、Select
<t:select t:id="color" model="literal:Red,Green,Blue"/>

根据前面的4部分内容,我们已经了解了Tapestry的基本概念,掌握了配置、组件等内容。现在我们通过剖析Tapestry的入门示例来对Tapestry进行一个总体上认识。
1、web.xml
<web-app>
    <display-name>app 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>t5demo</param-value>
    </context-param>
    <filter>
        <filter-name>app</filter-name>
        <filter-class>org.apache.tapestry.TapestryFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>app</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

这就是一个最简单的Tapestry应用所需要配置的内容了。
a.context-param中的tapestry.app-package配置,这在第一部分说过:这是Tapestry要求配置的java package的名称,Tapestry相关内容都需要在这个package下面的pages, services, componets子package下。这里的配置是t5demo
b.TapestryFileter的配置。这个非常容易理解,几乎所有现在流行的web框架都需要一个类似的定义。

2、start.tml以及相应的java class,例子中就是t5demo.pages.Start.java
Start.java非常简单,只定义了一个get方法:
public class Start
{
        public Date getCurrentTime()
        {
                return new Date();
        }
}
相应的页面start.tml
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">
    <head>
        <title>app Start Page</title>
    </head>
    <body>
        <h1>app Start Page</h1>

        <p> This is the start page for this application, a good place to start your modifications.
            Just to prove this is live: </p>

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


        <p>
            [<t:pagelink t:page="Start">refresh</t:pagelink>]
        </p>
    </body>
</html>

首先要注意在html的tag中加入了Tapestry的命名空间。
第二、${currentTime}就是Tapestry的Tag了,这里就会调用对应class的getCurrentTime方法在页面上显示对应的值。
第三、<t:pagelink>定义一个到自己本身页面的链接,来完成刷新的任务。t:pagelink在本系列的第4部分介绍过。

3、需要的library:
commons-codec.jar
javassist.jar
log4j.jar
slf4j-api.jar
slf4j-log4j.jar
tapestry5-annotations-5.1.0.5.jar
tapestry-core-5.1.0.5.jar
tapestry-ioc-5.1.0.5.jar

4、再加上一个log4j.properties,这就是一个最简单的tapestry应用所需要的全部东西了。
怎么样,感觉还是挺简单的吧。