Tapestry的使用感受、经验及存在问题
一、Tapestry简介
Tapestry是一个基于组件、监听事件的框架,它在web应用中的作用可以概括为“负责接受请求和产生HTML的响应”,它使用HTML模板、页面规范文件、对应的Java page class通过ognl表达式(Object-Graph Navigation Language)的相关对象属性与Tapestry组件的绑定替代了传统web应用中的jsp,以致于有人形容它开发Web应用类似开发传统的GUI应用。有关Tapestry的详细信息可查看相关资料,此处不再过多赘述。
二、Tapestry使用感受
初步接触Tapestry时获得的信息是Tapestry基于具有监听事件的组件的开发,能够使用其组件实现类似于搭积木一样开发web应用,类似开发传统的GUI应用,并且页面采用Html模板可以达到所见即所得的效果,咋一听给人以很美妙、奇特的感觉,但实际使用过程中才真正的体会到,无论什么样的web架构,都没有本质的区别,都是基于最基本的JSP+SERVELT+JAVABEAN模式的,万变不离其中,可以说其开发模式和struts、spring的MVC实质上是一致的;而Tapestry相对于其它开发框架的种种优点对开发人员的具体开发工作来说是很有限的(比如有人说:随着采用tapestry项目的深入,我们积累的各种积木也就越来越多,也就说,我们的工作越来越轻松,工作效率越来越高;但实际工作中却不是那么理想的),另外如果对Tapestry的基本原理了解的不是很透彻,使用Tapestry开发的将是效率低下、数据库压力巨大的web程序。
三、Tapestry的优点、使用经验杂谈
1、Tapestry提供的模板技术非常好,对于模板的侵入性特别的低
Tapestry可以让美工人员和开发人员尽可能的协同工作。这个优点恰恰是许多其它web框架的弱点。首先,利用可供利用的方便工具如DreamWeaver创建设计HTML页面,包括CSS,图片等。然后,在已经增加了动态内容的页面上修改,修改,再修改......直到客户满意为止。美工人员和开发人员之间的分工和协作能够起到很大的作用。
2、Tapestry的池化技术及page的初始化、入池、等待复用、调出复用的过程的个人理解
对Tapestry的池化技术深入理解有利于提高对tapestry的认识以及开发高效率的tapestry页面程序。因Tapestry是基于组件的,每个组件都是对象,在页面展示时对象都要实例化,tapestry的页面(page)也是组件, 为了提高效率tapestry采用了一种缓存机制,也就是页面池对page(页面)的实例进行缓存,以便复用,根据访问量的大小不同每个页面在页面池中的实例个数也有所不同。关于页面池的入池、出池的过程体现在page页面的Rendering过程中,如下图所示:
图1:page页面的Rendering过程图
需要注意的问题:a)如果page的属性不在initialize()方法中进行重置,如果保留属性值不重置是非常危险的,因为所有的page实例是完全共享的,另一个request完全有可能从池中获得上一个用户使用过的page,所以信息就会暴露。
b)page rendering过程中调用的java page class 的方法根据实际情况要进行isrending()、 is null、isEmpty()等等相关的处理避免同一个逻辑或数据操作多次执行。
² 小讨论
Ø 观点A:page页面的rendering过程是组件化开发的体现,用起来灵活,效果不错。
Ø 观点B:正是page这样的rendering过程给了程序员犯错误的机会,如果处理不好的话,开发出来的将是效率低下、数据库压力巨大的web程序。
3、page页面的Rewinding过程
rewind是用来处理表单提交的,tapestry的form、submit、directlink等组件都具有listener,其listener和java page class中的一个方法(监听方法)绑定,相关事件触发后开始page页面的Rewinding过程
图2:page页面的Rewinding过程图
需要注意的问题:在监听方法中一定要做好异常处理,并处理发生异常情况的页面的跳转问题。
² 小讨论
Ø 观点A:tapestry组件的数据回写的方式不错,只要将相关DTO绑定到组件上,form提交后就不用操心从request中取数据了,直接访问DTO就有数据了。
Ø 观点B:这算什么呀,现在好多架构基于commons-beanutils等公共组件都能做到这一点。
Ø 观点C:tapestry的基于事件、监听方法的模式不错呀,这样的话很容易就做到同一个页面甚至是同一个表单中不同的业务处理请求分别转发到不同的业务处理方法体中。
Ø 观点D:这算什么呀,struts、spring也能做到。
4、关于tapestry自定义组件
² 小讨论
Ø 观点A:tapestry自定义组件不错,可以自定义复用的组件。
Ø 观点B:jsp不也有自定义标签吗,同样能到到自定义组件达到的效果。
5、tapestry的Friendly URLs的配置
如果要访问contextpath下news目录中的Threads.html页面,需要通过地址/contextpath/app?page=news/Thread&service=page,这样的url即比较复杂又不能通俗易懂,通过一系列配置后可以使用/contextpath/news/Threads.html的url来访问
在hivemodule.xml配置文件中加入
<contribution configuration-id="tapestry.url.ServiceEncoders">
<page-service-encoder id="page" extension="html" service="page" />
<direct-service-encoder id="direct" stateless-extension="direct"
stateful-extension="sdirect" />
<extension-encoder id="extension" extension="svc" after="*" />
<asset-encoder id="asset" path="/assets" />
</contribution>
Web.Xml中加入
<servlet-mapping>
<servlet-name>tapestryservet name</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name> tapestryservet name </servlet-name>
<url-pattern>*.external</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name> tapestryservet name </servlet-name>
<url-pattern>*.direct</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name> tapestryservet name </servlet-name>
<url-pattern>*.sdirect</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name> tapestryservet name </servlet-name>
<url-pattern>*.svc</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name> tapestryservet name </servlet-name>
<url-pattern>/assets/*</url-pattern>
</servlet-mapping>
6、把‘*.page’与‘*.html’完全分开,不放在同一个目录下的方法
<page-specification >
<asset name="$template" path="context:WEB-INF/pagepath/pageName.html"/>
</page-specification >
7、Tapestry4.0.x版本的页面池实现很简单,只是使用一个map容器作为缓存,高并发的情况下容易导致OutOfMemoryException,解决方案如下:
添加java类
package com.zzxy.typestry;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.tapestry.event.ReportStatusEvent;
import org.apache.tapestry.event.ReportStatusListener;
import org.apache.tapestry.event.ResetEventListener;
import org.apache.tapestry.services.ObjectPool;
/**
* @author yanzhigang
*/
public class SoftReferenceObjectPool implements ObjectPool, ResetEventListener,
ReportStatusListener {
private String _serviceId;
private int _count = 0;
private Map _pool = new HashMap();
public synchronized Object get(Object key) {
List pooled = (List) _pool.get(key);
if (pooled == null || pooled.isEmpty())
return null;
_count--;
SoftReference reference = (SoftReference) pooled.remove(0);
// returns null if has been cleared by GC same as pool where empty
return reference.get();
}
public synchronized void store(Object key, Object value) {
List pooled = (List) _pool.get(key);
if (pooled == null) {
pooled = new LinkedList();
_pool.put(key, pooled);
}
SoftReference reference = new SoftReference(value);
pooled.add(reference);
_count++;
}
public synchronized void resetEventDidOccur() {
_pool.clear();
_count = 0;
}
public synchronized void reportStatus(ReportStatusEvent event) {
event.title(_serviceId);
event.property("total count", _count);
event.section("Count by Key");
Iterator i = _pool.entrySet().iterator();
while (i.hasNext()) {
Map.Entry entry = (Map.Entry) i.next();
String key = entry.getKey().toString();
List pooled = (List) entry.getValue();
event.property(key, pooled.size());
}
}
public void setServiceId(String serviceId) {
_serviceId = serviceId;
}
}
在hivemodule.xml配置文件中加入
<implementation service-id="tapestry.page.PagePool">
<invoke-factory>
<construct class="SoftReferenceObjectPool">
<event-listener service-id="tapestry.ResetEventHub"/>
<event-listener service-id="tapestry.describe.ReportStatusHub"/>
</construct>
</invoke-factory>
</implementation>
<implementation service-id="tapestry.GlobalObjectPool">
<invoke-factory>
<construct class="SoftReferenceObjectPool">
<event-listener service-id="tapestry.ResetEventHub"/>
<event-listener service-id="tapestry.describe.ReportStatusHub"/>
</construct>
</invoke-factory>
</implementation>
<implementation service-id="tapestry.request.EnginePool">
<invoke-factory>
<construct class="SoftReferenceObjectPool">
<event-listener service-id="tapestry.ResetEventHub"/>
<event-listener service-id="tapestry.describe.ReportStatusHub"/>
</construct>
</invoke-factory>
</implementation>
四、Tapestry使用中存在的问题及风险
1、 Tapestry文档资料少,而实践项目少;学习曲线陡峭,采用Tapestry开发的项目添加项目成员的成本比采用其它框架的项目大。
2、 Tapestry的缓存机制不完善,存在导致web应用内存溢出的嫌疑。
3、 Tapestry api不稳定,不向下兼容,版本变化带来的api变化巨大,如Tapestry3.0、Tapestry4.0、Tapestry5.0的差别很大,甚至Tapestry4.0、Tapestry4.1差别也比较大,这给项目升级带来了很大的困难。