Tapestry3.0开发概论

一、TAPESTRY技术的综述。

    tapestry平行struts/Spring MVC/webwork /jsf等开发工具的语言。tapestry是基于组件的开发工具。它具有组件的可重用性。因为大量重复使用的Tapestry组件,以及高度复用的表现层逻辑,使得工作效率大幅度提升。Tapestry组件是一个“黑盒子”,用于表现HTML响应,以及响应HTTP请求。

Tapestry组件通过其规范定义。规范是一个XML文档,其中定义了组件类型参数组件模板包含的组件以及被包含组件之间的联系,还有所有的assets

    Tapestryde:优点:

    1能够保证对HTML最少限度的干扰TapestryHTML页面的介入可以仅仅是增加一个jwcid(Java Web Component ID)属性。这个明确地划分了美工和程序员之间的界限。

    2页面的描述基于组件, Page规范描述了组件之间的联系,而java文件负责处理逻辑。

    3) 由于表现层逻辑全部放在了java文件里面,相比JAVA SCRIPT描述更加灵活

 4) 随着我们项目的深入开展,可复用的组件逻辑越来越多,开发的效率大大提高

    Tapestry框架是标准Servlet API的一种扩展。它需要J2SDK1.2或更高版本的J2SDK和一个与Servlet API2.2(或更高)兼容的应用服务器/Servlet容器。

    一个Tapestry应用由许多拥有唯一名称的页面组成。一个页面由一个模板和一些可复用的控件构成。模板由标准的HTML标签和一些额外的属性和标签构成,这些额外的属性和标签是为了告诉Tapestry框架这个页面的那些部分是由Tapestry控件组成。

    Tapestry应用拥有高度的可升级性,它利用缓存和对象池使每个请求的处理时间最小化。Tapestry应用拥有跟传统Servlet应用相仿的性能。

    Tapestry的学习曲线会长一点,因为它与流行的Web应用框架不太相同。

    注意我们需要三个文件:*.html/   *.page/  *.java,分别表示了HTML的模板页面属性逻辑属性

    首先简介一下web.xml这可以使我们了解TAPESTRY是怎么工作的。

    web.xml的描述:

<?xml version="1.0"?>
<!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>test</display-name>
<servlet> <servlet-name>tapestry</servlet-name> <servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>tapestry</servlet-name> <url-pattern>/app</url-pattern> </servlet-mapping> </web-app>

    因为Tapestry框架是标准Servlet API的一种扩展,它实际上是建立在J2EE框架上的。这个从WEB.XML的描述中可以看出.j2eeURL的请求中分离出/app=>tapestry这个servlet=>servlet-class org.apache.tapestry.ApplicationServlet处理类.下面就与J2EE SERVLET处理的差不多了

     可以猜测org.apache.tapestry.ApplicationServlet做了以下工作:

     1)拦截HTML文件,找到页面描述,创建类A,将其中的jwcid属性变换为对A的调用从而获取属性值.进而获得页面实例.A可能在创建之后放入对象缓冲池,以备后来使用。

     2)当提交表单时,处理listener,处理页面表单逻辑.

     3)其他工作同Struts Servlet类似.

二、在Windows下如何使用Tapestry

    1. 从官方网站:http://tapestry.apache.org/download.html下载tapestry-bin-5.1.0.5.zip并解压到:D:\tapestry\tapestry-project-5.1.0.5 ,需要指出的是D:\tapestry\tapestry-project-5.1.0.5\lib下包含了开发一个Tapestry应用的全部jar

    2. 安装myeclipse6.0

    3. 下面介绍如何创建一个简单的tapestry应用启动 myeclipse 新建 java web project.展开工程(test)右键点击JRE System library 选择 build path >> configure build path. 新对话框中选择 libraries 选项卡,点击 add library>>user library>>user libraries>>new ,起一个名字本文中为T5-lib 然后点击 add JARs D:\env\tapestry-project-5.1.0.5\lib 目录下的全部jar文件导入. 然后选中T5-lib 点击finish 。修改web-infweb.xml文件,同前面例子。

    4. WebRoot目录下(web-inf同级目录),创建Home.html。(Tapestry程序入口名字在.appliaction文件中定义)代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    <span jwcid="@Insert" value="ognl:HelloWorld" />
</body>
</html>

    5. web-inf目录下创建名为 Home.page xml文件。如下:

<?xml version="1.0" encoding="GBK"?>
<!DOCTYPE page-specification PUBLIC
"-//Apache Software Foundation//Tapestry Specification 4.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd">
<page-specification class="com.kevin.tapestry.Home"> </page-specification>

    6. 最后在 mytapestry 包下写一个java

package com.kevin.tapestry;
import org.apache.tapestry.html.BasePage;

public abstract class Home extends BasePage{

     public String getHelloWorld(){
        return "Hello World!";
     }
}

    7. 启动tomcat 在浏览器输入 http://localhost:8080/test/app 应该看到如下页面

        Hello World!

.Tapestry框架的简介

    一个基于Tapestryweb应用可能包含了以下几种文件:应用规范文件hivemind配置文件HTML模板文件页面规范文件页面类文件组件包规范文件组件规范文件组件类文件动态脚本文件。下面一个一个来详细讲述。

1. 三种配置文件(应用程序规范文件,hivemodule配置文件,组件包规范文件)

1.1 应用程序规范文件:是一个以应用程序servlet名称命名的,以“.application”为扩展名的xml文件。一个web应用只能有一个应用程序规范文件。他制定了应用程序的各种细节配置,页面和组件配置,组件包配置等等。如果我们不为应用程序配置应用程序规范文件,Tapestry会为我们提供一个默认的。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE application PUBLIC
 "-//Apache Software Foundation//Tapestry Specification 4.0//EN"
 "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd">
<application name="test"> <meta key="org.apache.tapestry.page-class-packages" value="test_path"/> <library id="contrib" specification-path="/org/apache/tapestry/contrib/Contrib.library"/> <page name="Home" specification-path="Home.page"/> </application>

  <application>为根标签元素,name制定了serlvet名称。

  <meta>用于配置应用程序细节。在此我们为应用程序配置页面类的默认定义包路径。那么Tapestry将自动在test_path路径下寻找页面类。

  <library>导入了外部组件包.

1.2 hivemind配置文件:HiveMind框架是一个依赖注入微核心框架,Tapestry框架构建在HiveMind之上。如果你的程序业务层没有用到hivemind,不必配置hivemodule文件。下面给了一个为应用程序配置Friendly

URL的例子:

<?xml version="1.0" encoding="UTF-8"?>
    <module id="test" version="1.0.0">
        <contribution configiration-id="tapestry.url.ServiceEncoders" />
            <direct-service-encoder id ="direct" stateless-extension="direct" stateful-extension="sdirect" />
            <direct-service-encoder id ="action" stateless-extension="action" stateful-extension="saction" />
            <page-service-encoder id="page" extension="page" service="page" />
            <page-service-encoder id="external" extension="external" service="external" />
            <asset-encoder id="asset" path="/assets" />
            <extension-encoder id="ext" id="ext" extension="svc" after="*"/>
    </contribution>
</module>

1.3 组件包规范文件: 为了跨项目积累组件,我们可以将组件打成jar包,然后通过在应用程序规范文件中引入组件包的方式,调用组件包中的自定义组件。组件包规范文件的后缀为“.library”的XML文件,命名任意。

2. Tapestry页面的组成

2.1 HTML模板:HTML模板就是标准的静态HTML页面,在HTML模板中我们通过jwcid(Java Web Component ID)标签属性调用Tapestry组件。例如上例中的 <span jwcid=”@Insert” value=”ognl:HelloWorld” />

在上面的代码中,调用了Tapestry的官方组件Insertvalue属性是Insert组件的参数之一。“@”符号用于区分jwcid是一个组件类型还是一个组件ID,如果是一个组件IDtapestry将在HTML模板对应的页面规范中寻找对应该组件ID的组件调用配置。(亦可这样理解,“@”为官方组件“@”为自定义组件,自定义组件需要在.page”文件中详细配置该组件的用法。)

2.2 页面规范:是以页面名称命名且后缀是.pageXML文件,这个后缀为.pageXML文件必须声明Tapestry的页面规范DOCTYPE

例如在上例中:

<span jwcid="@Insert" value="ognl:HelloWorld" />   

也可以这样来表示:

HTML模板:

 <span jwcid="@Insert" value="ognl:HelloWorld" />

页面规范:

<?xml version="1.0" encoding="GBK"?>
<!DOCTYPE page-specification PUBLIC
 "-//Apache Software Foundation//Tapestry Specification 4.0//EN"
 "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd">
      
<page-specification class="com.kevin.tapestry.Home">
    <component id="test" type="Insert">
        <binding name="value" value="HelloWorld" />
    </component>
</page-specification>

2.3 页面类:必须继承org.apache.tapestry.html.BasePage类,如:

package com.kevin.tapestry;
import org.apache.tapestry.html.BasePage;   
public abstract class Home extends BasePage{ public String getHelloWorld(){ return "Hello World!"; } }

3. Tapestry组件的组成

  在Tapestry中,一个组件通常由HTML模板组件规范组件类动态脚本文件4个文件组成,除了组件规范以外,其他三个文件都不是必须的。

4. ognl

  OGNLObject Graph Navigation Language 的简称,是一种绑定方式的表达式语言。

  OGNLOpenSymphony的一个开源项目。OGNL最重要的也是最根本的作用是简化调用Java类中的getter/setter方法,同时,他也是一种功能单一且易于使用的表达式语言,OGNL表达式就式该语言的全部。对于OGNL如何与模板对象绑定,Tapestry已经实现,我们不需要关心。同时,虽然OGNL表达式带有运算功能,但是Tapestry框架将页面逻辑全部放到了页面类中,因此根本没必要使用OGNL表达式来处理页面逻辑。

Tapestry框架的整个调用过程Home.html -> Home.page -> Home.class 

四、各种组件的使用(只列举了一些常用的,更多请参见TAPESTRY官方网站)

1Foreach

   source:是对应的java类里的List 对象或者是个数组 需要抽象 或者提供set get方法

   value:是循环这个source对象代表当前的一个 ,需要在page文件中设置问一个属性,可以不在对应的java类里有这个属性

   index:是循环的索引值 value一样 在page文件中设置一个属性即可 <property name=”index”/>

在循环的时候 会自动为vlaueindex赋当前的值

<table cellspacing="6">
  <tr>
   <td>ID</td>
   <td> </td>
   <td>Name</td>
   <td> </td>
   <td>Level</td>
  </tr>
  <tr>
   <td colspan="5"><hr></td>
  </tr>
  <tr jwcid="@Foreach" source="ognl:customerList" value="ognl:customer" element="tr">
   <td><span jwcid="@Insert" value="ognl:customer.id"/></td>
   <td> </td>
   <td><span jwcid="@Insert" value="ognl:customer.fullName"/></td>
   <td> </td>
   <td><span jwcid="@Insert" value="ognl:customer.memberLevel"/></td>
  </tr>
</table>
<property-specification name="customerList" type="java.util.List" persistent="yes"/><property-specification name="customer" type="Customer"/>
public abstract class SalesPage extends BasePage {

    public abstract List getCustomerList();
    public abstract List setCustomerList(List value);

}

public class Customer implements Serializable {

    private Integer id;
    private String fullName;
    private String memberLevel;

    public Customer(Integer id, String fullName, String memberLevel) {
        this.id = id;
        this.fullName = fullName;
        this.memberLevel = memberLevel;
    }

    public Integer getId() { return id; }
  
    public String getFullName() { return fullName; }

    public String getMemberLevel() { return memberLevel; }

}

    调用过程如下:source=”ognl:customerList”==getCustomerList()==》得到一LIST

    LISTFOREACH每次循环LIST中顺序取出一对象A(类型为ognl:customer),该对象AFOREACH循环中它的属性被引用(调用:getId() getFullName(), getMemberLevel() )。

2Hidden组件 总是多余的处理

 <input jwcid="@Hidden" type="hidden" value="ognl:blahblah" encode="false"/>

  用来存储一些页面的状态变量。

3Insert 组件

<input type="text" jwcid="name@Insert" value="ognl:user.name"/>

    页面表现时将会到页面类中寻找getUser().getName()方法获取初值并输出相当于在页面上显示数据.

4TextField 组件 

<input type="text" jwcid="username@TextField" value="ognl:username"/>

  页面表现时,将会到页面类中寻找getUsername()方法获取初值

    *如果是修改信息页面通常初始值要在页面表现之前由setUsername()手动设置从数据库中读取出来的值表单提交时通过setUsername()写入新值(即用户输入值)在类中通过getUsername()获取新值相当于在修改个人信息时首先读出用户名赋予文本框(用户名)初值,用户修改时填入新值,后台获取之后,*Hidden属性区分是普通文本输入框(默认false)和密码输入框(hidden=”ognl:true”)

  readonly属性设置只读 readonly=”true”为只读(后台可读取)

  *disabled属性设置是否可写 diabled=”true”为不可写(后台也不可读取)

6TextArea 组件

<textarea jwcid="content@TextArea" value="ognl:content" cols="40" rows="10"></textarea>

  页面表现时,将会到页面类中寻找getContent()方法获取初值工作原理同TextField。

7RadioGroup/Radio 组件

 <span jwcid="headImage@RadioGroup" selected="ognl:headImage">

      <input jwcid="@Radio" type="radio" value="1"/>头像1
      <input jwcid="@Radio" type="radio" value="2"/>头像2
      <input jwcid="@Radio" type="radio" value="3"/>头像3
      <input jwcid="@Radio" type="radio" value="4"/>头像4
      <input jwcid="@Radio" type="radio" value="5"/>头像5
      <input jwcid="@Radio" type="radio" value="6"/>头像6

    </span>

    RadioGroup为每一个Radio提供一个唯一的IDRadioGroup跟踪当前被选中的属性值,并且只有一个Radio能够被选中

    页面提交时,RadioGroup组件就利用OGNL表达式向headImage字段写入被选中的Radio组件的value参数值

    页面表现时(修改页面)将会到页面类中寻找getHeadImage()方法获取初值然后寻找@Radio组件中与其相同的组件并勾选上

8PropertySelection 组件

  使用PropertySelection组件必须要构造一个类来实现IPropertySelectionModel接口,并且重写该接口的5个方法

  public int getOptionCount() //提供下拉菜单的长度

    public Object getOption(int index) //提供select标签的option

    public String getLabel(int index) //提供select标签的Label值,也就是下拉菜单显示的内容

    public String getValue(int index) //提供select标签的value值

    public Object translatue(String value) //selected后的返回值,value值未必就是我们需要的返回值,可以在这个方法里面对返回的value做对应的转换或修改.

8.1属性下拉框

<select jwcid="gender@ProPertySelection" name="genderSelect" value="ognl:gender" model="supportedGender">
      <option selected>先生</option>
      <option>女士</option>
</select> 

代码:GenderSelectionModel.java  

public class GenderSelectionModel implements IPropertySelectionModel {  
    public static final String male = "先生";  
    public static final String female = "女士";  
    public static final String[] genderOptions = { male, female };  

    public int getOptionCount() {  
        return genderOptions.length;  
    }  

    public Object getOption(int index) {  
        return this.translatue(genderOptions[index]);  
    }  

    public String getLabel(int index) {  
        return genderOptions[index].toString();  
    }  

    public String getValue(int index) {  
        return genderOptions[index];  
    }  
    public Object translatue(String value) {  
        if (value.equals("先生")) {  
            return "1";  
        } else {  
            return "0";  
        }  
    }  
}

代码:ModUserInfo.java  

public IPropertySelectionModel getSupportedGender() {  
    return new GenderSelectionModel();  
}

  存入数据库中“1”代表先生,”0″代表女士通过translatue(String value)方法转换页面表现时通过model属性给出的IPropertySelectionModel获取下拉选项getSupportedGender()。

  然后通过getGender()方法获取初值比如获取“0”,则在页面显示时寻找value值为“0”的选项即为女士,并选择之作为初始选择项。

8.2日志类型下拉框

<select jwcid="logType@PropertySelection" name="typeSelect" value="ognl:logType" model="supportedType">
      <option>心情日记</option>
      <option>情感天地</option>
      <option>生活感触</option>
</select>

代码:TypeSelectionModel.java  

public class TypeSelectionModel implements IPropertySelectionModel {  

    private List typeList = new ArrayList();  

    public TypeSelectionModel(List typeList) {  
        this.typeList = typeList;  
    }  

    public int getOptionCount() {  
        return typeList.size();  
    }  

    public Object getOption(int index) {  
        return ((LogType)typeList.get(index)).getValue();  
    }  

    public String getLabel(int index) {  
        return ((LogType) typeList.get(index)).getName();  
    }  

    public String getValue(int index) {  
        return ((LogType) typeList.get(index)).getValue();  
    }  

    public Object translatue(String value) {  
        return value;  
    }  
}

代码:ModLog.java  

public IPropertySelectionModel getSupportedType() {  

    TypeSelectionModel typeSelectionModel = new TypeSelectionModel(loadType(getUser().getUserId()));  
    return typeSelectionModel;  
}  

private List loadType(int userid) {  
    ...//从数据库载入该用户的日志类型列表  
}

  页面表现时,通过model属性给出的IPropertySelectionModel获取下拉选项getSupportedType(),然后通过value属性给出的初始值即,getLogType()方法获取初值比如获取“2”,则在页面显示时寻找value值为“2”的选项即为生活感触,并选择之作为初始选择项。

9Form组件

 <form jwcid="logForm@Form">

      ...

    </form>

    Form的监听(listener)方法可以有两种方式:

9.1. Form组件中声明

 <form jwcid="logForm@Form" listener="ognl:listener:onLogin">

          ...

        </form>

9.2. submit类型组件中声明

<input type="submit" jwcid="onLogin@Submit" listener="listener:onLogin" value="发表"/>

或者 <span jwcid="@ImageSubmit" image="..." listener="listener:onLogin"><img src="..." width="" height=""/></span>

  前一种方式当Form中只要有submit就会触发监听方法后一种方式是Form中有多个submit,各自实现不同的监听方法。

10 Conditional 组件

 <span jwcid="@Conditional" condition='ognl:item.sex.equals("1")'>先生</span>

    <span jwcid="@Conditional" condition='ognl:item.sex.equals("0")'>女士</span>

    conditional参数为true时运行Conditional组件中的HTML模板内容.

    Tapestry4.0以后就不支持该组件了, 可以使用其他组件来实现:

10.1. Contrib:ChooseContrib

When contrib:外围爱好者根据需要自行编译并贡献的软件)

<library id="contrib" specification-
path="classpath:/org/apache/tapestry/contrib/Contrib.library"/>(.application文件中引入Contrib类包)
    <span jwcid="@contrib:Choose">
      <span jwcid="@contrib:When" condition='ognl:user.gender.equals("1")'>先生</span>
      <span jwcid="@contrib:When" condition='ognl:user.gender.equals("0")'>女士</span>
    </span>

10.2. If组件

<span jwcid="@If" condition='ognl:item.sex.equals("1")'>先生</span>

    <span jwcid="@If" condition='ognl:item.sex.equals("0")'>女士</span>

10.3. Else组件

  <span jwcid="@Else">man</span>