一、TAPESTRY技术的综述。
tapestry是平行于struts/Spring MVC/webwork /jsf等开发工具的语言。tapestry是基于组件的开发工具。它具有组件的可重用性。因为大量重复使用的Tapestry组件,以及高度复用的表现层逻辑,使得工作效率大幅度提升。Tapestry组件是一个“黑盒子”,用于表现HTML响应,以及响应HTTP请求。
Tapestry组件通过其规范定义。规范是一个XML文档,其中定义了组件类型,参数,组件模板,包含的组件以及被包含组件之间的联系,还有所有的assets。
Tapestryde:优点:
1) 能够保证对HTML最少限度的干扰,Tapestry对HTML页面的介入可以仅仅是增加一个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的描述中可以看出.j2ee从URL的请求中分离出/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: apestry apestry-project-5.1.0.5 ,需要指出的是D: apestry apestry-project-5.1.0.5lib下包含了开发一个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 apestry-project-5.1.0.5lib 目录下的全部jar文件导入. 然后选中T5-lib 点击finish 。修改web-inf下web.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框架的简介
一个基于Tapestry的web应用可能包含了以下几种文件:应用规范文件,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的官方组件Insert,value属性是Insert组件的参数之一。"@"符号用于区分jwcid是一个组件类型还是一个组件ID,如果是一个组件ID,tapestry将在HTML模板对应的页面规范中寻找对应该组件ID的组件调用配置。(亦可这样理解,"@"为官方组件,无"@"为自定义组件,自定义组件需要在“.page”文件中详细配置该组件的用法。)
2.2 页面规范:是以页面名称命名且后缀是.page的XML文件,这个后缀为.page的XML文件必须声明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
OGNL是 Object Graph Navigation Language 的简称,是一种绑定方式的表达式语言。
OGNL是OpenSymphony的一个开源项目。OGNL最重要的也是最根本的作用是简化调用Java类中的getter/setter方法,同时,他也是一种功能单一且易于使用的表达式语言,OGNL表达式就式该语言的全部。对于OGNL如何与模板对象绑定,Tapestry已经实现,我们不需要关心。同时,虽然OGNL表达式带有运算功能,但是Tapestry框架将页面逻辑全部放到了页面类中,因此根本没必要使用OGNL表达式来处理页面逻辑。
Tapestry框架的整个调用过程:Home.html -> Home.page -> Home.class
四、各种组件的使用(只列举了一些常用的,更多请参见TAPESTRY官方网站)
1、Foreach
source:是对应的java类里的List 对象或者是个数组 需要抽象 或者提供set get方法
value:是循环这个source对象代表当前的一个 ,需要在page文件中设置问一个属性,可以不在对应的java类里有这个属性
index:是循环的索引值 同value一样 在page文件中设置一个属性即可 <property name="index"/>
在循环的时候 会自动为vlaue和index赋当前的值
<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。
该LIST在FOREACH每次循环LIST中顺序取出一对象A(类型为ognl:customer),该对象A在FOREACH循环中它的属性被引用(调用:getId() ,getFullName(), getMemberLevel() )。
2、Hidden组件 总是多余的处理
<input jwcid="@Hidden" type="hidden" value="ognl:blahblah" encode="false"/>
用来存储一些页面的状态变量。
3、 Insert 组件
<input type="text" jwcid="name@Insert" value="ognl:user.name"/>
页面表现时,将会到页面类中寻找getUser().getName()方法获取初值并输出相当于在页面上显示数据.
4、 TextField 组件
<input type="text" jwcid="username@TextField" value="ognl:username"/>
页面表现时,将会到页面类中寻找getUsername()方法获取初值。
*如果是修改信息页面,通常初始值要在页面表现之前由setUsername()手动设置从数据库中读取出来的值表单提交时,通过setUsername()写入新值(即用户输入值),在类中通过getUsername()获取新值相当于在修改个人信息时,首先读出用户名赋予文本框(用户名)初值,用户修改时填入新值,后台获取之后,*Hidden属性区分是普通文本输入框(默认false)和密码输入框(hidden="ognl:true")
readonly属性设置只读 readonly="true"为只读(后台可读取)
*disabled属性设置是否可写 diabled="true"为不可写(后台也不可读取)
6、 TextArea 组件
<textarea jwcid="content@TextArea" value="ognl:content" cols="40" rows="10"></textarea>
页面表现时,将会到页面类中寻找getContent()方法获取初值,工作原理同TextField。
7、 RadioGroup/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提供一个唯一的ID。RadioGroup跟踪当前被选中的属性值,并且只有一个Radio能够被选中。
页面提交时,RadioGroup组件就利用OGNL表达式向headImage字段写入被选中的Radio组件的value参数值。
页面表现时(修改页面),将会到页面类中寻找getHeadImage()方法获取初值,然后寻找@Radio组件中与其相同的组件并勾选上。
8、 PropertySelection 组件
使用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"的选项即为"生活感触",并选择之作为初始选择项。
9、 Form组件
<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:Choose和Contrib
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>