JSF VS Tapestry 全面比较

目 前应用很广泛而且也很成熟的Struts应用开发框架,在未来几年里将会逐渐淡出,其基于Action(action-based)的开发模型也将被活跃 的java社区所推崇的新的开发模型所替代,对于java开源社区来说,追求完美是很多人的理想。在2000年初诞生的Struts以及其他类似的 MVC(Model View Controler)框架主要都是以操作为中心,且都是无状态的开发模型,而现在,基于组件(component-based)和事件驱动(event- driven)的开发模型逐渐流行起来,在很多时候都成了Struts的有利竞争者,这其中来自jcp的jsr127-JavaServer Faces(JSF)以及来自apache的Tapestry是其中的佼佼者。

    在这篇文章中,我们将把这两种框架JSF和Tapestry进行详细的对比。我们的比较将涉及到两种框架的设计、运行环境以及如何开发。比较的目的在于让 读者了解这两种框架各自的优缺点,以便于在自己的项目中,根据实际情况,选择合适的框架。该文章的内容基于JSF1.1和Tapestry3.0.3 (在个别地方由于需要会谈到Tapestry4.0)。
   JSF和Tapestry简介  
   JSF源于JCP(Java Community Process)的JSR127规范。Sun公司自己同时也对该规范提供了一个实现,目前的版本是1.1,而且该项目的设计者之一正是Struts的作者 Craig McClanahan。另外一个JSF的实现,就是Apache的一个项目MyFaces。目前,几个主流的java开发工具厂商都在其java ide中提供了对jsf的支持,比如Sun、Oracle、IBM、Borland等,而且JSF的后续版本JSF1.2也将成为J2EE5.0的一个组 成部分。    
   Tapestry最初于2000年在SourceForge注册,来自于Apple WebObjects,随后,于2003年成为Apache的一个项目。与JSF不同的是,Tapestry并不是某一个jsr规范的实现,它仅仅是一个 开源项目,当前版本是3.0.3。4.0版本预计也将在后面的一段时间内推出。
   JSF和Tapestry作为MVC框架,在一些基本特性上是非常类似的:

  •     它们都让开发者不再直接与Servlet API打交道,而是让开发者在一个更高的抽象层上思考问题;
  •     它们都将web页面上的显示元素绑定到一个java对象的某个属性上,这些属性可能是字符串、数字、日期或者其他类型,并且由该对象来维护其状态。用户在页面上的交互行为(比如用鼠标点击一个按钮或者链接)都直接映射为java类中的一个事件处理方法;
  •     两种框架都支持组件式的开发方式,并且开发的组件可被其他开发者重用。两种框架都自带一个标准组件库,提供web开发的常见通用功能。

   在下面的章节里面,我们将会看见这两个框架在各自的实现方式上是有很大区别的。对于大多数程序员来说,基于两种框架做开发,将是非常不同的两种体验。 

Sidebar: 例子程序
   在下面本文提供了一个例子程序,该程序的代码大部分都将在这片文章中做出详细的描述。这个程序主要就是一个管理个人假期的工具,它的主要功能包括:
   * 一个home界面, 列出所有登记的假期,包括假期开始时间、天数以及一下描述信息;
   * 一个detail界面,用于浏览某一登记假期的详细信息;
   * 一个new界面,用于添加一个假期信息。

   下面这幅图展示了该程序的主体流程和功能:
JSF VS Tapestry 全面比较

   你可以从下列地址下载该程序基于不同框架的源代码:
   JSF version
   Tapestry version

   对比1: 页面开发(Page Development)
   一个web应用实际上就是后端用java代码获取相应数据,并将数据传递给前端表现层代码,然后最终返回给终端用户。因此,在一个开发人员看来,当他初次 接触JSF和Tapestry时,最直接的感觉就是JSF的表现层是基于JSP的模板技术,而Ta pestry的表现层模板基本就可以看作是一个HTML。

   JSF
   JSF采用JSP的技术作为其表现层技术。与标准兼容的JSF实现必须实现一个核心组件的JSP标签集。下面的代码就是一个使用了JSF组件标签的Html代码:

代码:
<h:form>      
<h:panelGrid columns="2" border = "1">
<h:outputText styleClass = "label" value="No"/>
<h:outputText value="#{holidaySession.currentHolidayBooking.holidayID}"/>
<h:outputText styleClass = "label" value="Date"/>
<h:outputText value="#{holidaySession.currentHolidayBooking.date}"/>
<h:outputText styleClass = "label" value="Number of days"/>
<h:outputText value="#{holidaySession.currentHolidayBooking.amount.value}"/>
<h:outputText styleClass = "label" value="Description"/>
<h:outputText value="#{holidaySession.currentHolidayBooking.description}"/>
</h:panelGrid>
<BR>
<h:commandButton value="Back" action="#{holidaydetail_backing.home}" immediate = "true"/
</h:form>

 含有JSF标签的html页面不能在标准浏览器中预览。要想浏览,必须使用JSF设计工具或者直接部署到应用中,在真实运行环境中浏览。
   对于JSF技术来说,其表现层技术就是JSP,但是这不是唯一的解决方法。Hans Bergsten的文章介绍了可供选择的其他的方法,读者可阅读该文章获取详细信息,Improving JSF by Dumping JSP 。 Bergsten的文章中指出了混合JSF和JSP标签暴露出的一些问题。当然,这些问题在JSF1.2和JSP2.1中会得到解决。

   Tapestry
   对于大多数的Tapestry应用来说,Tapestry的表现层模板看起来就是一个简单的规则的Html,只不过其中加入了一些Tapestry的属性。下面是一段例子代码:

代码:
<span jwcid = "@Conditional" condition = "ognl:currentHolidayBooking">  
<p><strong>Holiday Details</strong></p>
<table>
<tr>
<td class = "label">No</td>
<td><span jwcid = "@Insert"
value = "ognl:currentHolidayBooking.holidayID">1</span>
</td>
</tr>
<tr>
<td class = "label">Start date</td>
<td><span jwcid = "@Insert"
value = "ognl:currentHolidayBooking.date" format = "ognl:dateFormat">1</span>
</td>
</tr>
<tr>
<td class = "label">Number of days</td>
<td><span jwcid = "@Insert"
value = "ognl:currentHolidayBooking.amount.value">1</span>
</td>
</tr>
<tr>
<td class = "label">Description</td>
<td><span jwcid = "@Insert"
value = "ognl:currentHolidayBooking.description">1</span>
</td>
</tr>
</table>
</span>
 

  大家可以尝试把这段代码放入一个Html的body标签当中,你会发现浏览器可以正常的观看它。
   上面的例子中,jwcid = "@componentName"属性就是定义了一个Tapestry标签。
   Tapestry的模板不仅仅可以用HTML作为载体,它也支持其他的一些标记语言,Tapestry标签是具有良好格式的标签,即必须成对出现。 Tapestry模板技术支持的标记语言典型的就是HTML以及用于无线应用的WML。其最大的一个特点 就是,可脱离Servlet容器,直接预览。
   事实上JSF标签由于不是标准的html标签,使得它对于初学者来说,是难于使用的。而对于很多java程序员来说,他们喜欢编辑HTML代码,至少是乐意编写html代码。
   JSF技术宣称的一个技术优点就是,可使用同一个模板编写运行在不同设备上的应用,由此带来很大的灵活性。然而,这样做,由于要协调不同设备间的表现差异 性,那么很可能同一个模板,将不能正好表现你的输出。同时,你不得不学习新的标签库的使用方法,并且搞懂它们是怎样映射到html的。随着时间的推移, JSF标签的简洁可帮助你很快的编写表现层代码,同时也降低了开发者对jsf设计工具的依赖。
   本文认为,JSF的学习成本高于其技术优点。在大多数情况下,我们并不需要编写适应于不同设备的应用。尽管JSF设计工具提供了简单的图形化的方法来构建 和预览JSF应用,但是在一个开发中,页面设计人员更多的是喜欢用流行的HTML设计工具来编写和预览页面,这就发生了一个冲突,即只有将更多的页面工作 转移到java程序员身上,因为一个页面设计人员通常情况下是不乐意去操作JSF设计工具的。
   JSF开发者一直在寻找一种解决这些问题的方法。JSF技术设计良好的扩展体系,使得这成为可能,其中一个技术浮现出来,那就是表现层控制器。一个非常有前途的表现层控制器的实现就是Facelets ,由java.net创建的开放源代码项目。Facelets的灵感就来源于Tapestry的模板模型,这使得JSF不再依赖于JSP技术。Facelets允许开发者创建Tapestry风格的标签,就像下面这段代码一样:

代码:
    <input id="bar" type="text" jsfc="h:inputText" value="#{foo.bar}"/>

  在未来的一段时间内,Facelets将会被开发者所采用,或许会影响JSF未来的版本。
   和JSF比较,Tapestry在表现层方面基于HTML代码,可被标准浏览器所浏览,这正是它在表现层上的优势所在。
   比较2: java编程模型(Java Programming Model)
   在前面我们提到Tapestry和JSF都允许表现层的模板直接和一个Java类中的属性和方法进行交互,那这些类的实例在运行时是怎么创建和管理的呢?
   Tapestry
   Tapestry的一个完整过程通常都要包括三个部分:用于显示的页面模板(一般就是html),带有相关属性和方法的java类,用于定义页面模板上的 控制元素和java类的关系的页面定义文件。Tapestry有着一套特殊的访问HttpSess ion、ServletContext的体系。
   在一个页面定义文件中描述所有的数据绑定是可以的,但是一个页面控制元素却不能在request周期内,绑定一个java类。在request周期内只能 通过一个page类访问一些属性和方法。这其中最主要的限制就是关于page类的问题,一个pag e类必须是BasePage或者AbstractPage的派生类。从另一方面来说,这就意味着你所编写的表现层逻辑的代码将会和框架本身的实现有着非常 紧密的耦合。不过,Tapestry未来的版本就会减少这种耦合,努力成为一个松耦合的编程模型。

   JSF
   在JSF应用中没有页面定义文件。它只有一个全局的配置文件,命名为:faces-config.xml,里面通常都定义了一堆"managed beans"。这些managed beans都是带有属性和事件监听器的定义良好的java bean。在faces-config.xml中定义的后端bean都有三个参数:一个标识符、一个java类名、一个bean的生存周期,生存周期可以 是request、session、application中的一种。一旦在 faces-config.xml中定义好了一个managed bean,那一个前端页面上的显示控制元素就可以使用标识符来关联这个bean。managed bean也可以配置为引用另外一个managed bean。
   JSF和Tapestry都可以方便的与其他的中间层技术整合,比如Spring。JSF managed bean facility 是一个IoC(Inversion of Control)。通过诸如 JSF-Spring 这 样的扩展技术,我们可以方便将其和Spring很好的整合在一起,使得JSF的表达式可以调用Spring的bean的方法。虽然Spring可以与 Tapestry3.0整合,但是在Tapestry4.0当中才能更好的充分发挥IoC特性;Tape stry的领导Howard Lewis Ship已经在Tapestry启动了IoC框架的工作。Spring的bean将会很轻松的注入到Tapestry应用的类中。
   JSF的编程模型提供了更大的灵活型,因为你可以通过组合的方式来丰富你的代码功能。比如,你可以设定一个指定的managed bean完成页面的某些功能,同时,你也可以在这些bean里引用其他的managed bena,从而可在页面间共享一些功能。
而Tapestry必须使用类继承的方法,也就是说,Tapestry应用中的一个页面类必须从框架指定的基类中派生(包含一大堆框架指定的状态),这并不是一个理想的方法。
JSF能够非常直观的管理session和application周期内的状态: 页面代码可以方便的访问managed bean,而不管其生命周期是request、session还是application。而Tapestry在这方面就相对差点,不过, Tapestry4.0在这方面做了很大的改进,引入了类似于JSF managed bean的技术,同时,还支持Java 5.0的annotaions技术,减少对XML配置文件的依赖,从而降低应用配置的复杂性。
   JSF在编程模型上来看,与Tapestry相比具有更大的灵活性。

对比3: 请求处理生命周期(Request Processing Life Cycle)
   请求处理生命周期在一个web应用中是很重要的,它反映了一个请求从提交到将信息返回给客户端的处理过程。当然,请求处理生命周期必须能以某种优雅的方式,在正确的地方插入用户定制的请求处理操纵逻辑。
   JSF
   JSF的请求处理生命周期清晰的定义成六个步骤:Restore View、Apply Request Values、 Process Validations、 Update Model Values、 Invoke Application 和 Render Response。从第二步Apply Request Values开始,可以直接跳到最后一步Render Response,甚至可以直接返回给客户端并且通知JSF运行时,响应操作已经完成了。有些方法要访问JSF的FacesContext对象,比如状态 监听器(phase listeners)、事件操纵器(event handlers)、转换器(converters)、验证器(validators)等,这都可能忽略请求处理生命周期中的某些步骤。
   Tapestry
   JSF有一个单一的生命周期模型,而Tapestry的生命周期依赖于其调用的引擎服务(engine service)。 每一个引擎服务(engine service)都有其自己的生命周期。比如,Tapestry中的Direct Service控制标单的提交,而Page Service用于渲染页面,并且不需要额外的服务端操作。每一个引擎服务(engine service)都被设计成在自身的生命周期内完成一些特定的任务。这就意味着,对于一个特定的需求,你可以创建一个对应的引擎服务(engine service),并且可自己定制生命周期。
   JSF的生命周期概念更容易理解,而Tapestry可对一个特定的操作定义一个生命周期,这在某些问题的解决上,可能会提供更优雅的解决方案。
   对比4: 页面导航(Navigation)
   在web应用中,通常有两种方式进行页面间的跳转,一种是通过HTTP POST方式提交一个表单,然后跳转到我们的目的页面;一种是通过点击某个链接,通过url带参数的方式跳转页面。JSF和Tapestry都大量的使用 了postback机制,即在服务端处理页面间跳转,尽管如此,这两种框架在具体的实现上还是有很大的 差别的。
   Tapestry
   在一个Tapestry应用中,当用户点击一个链接或者提交一个表单时,一个服务器端的监听方法就会被调用。这个监听方法中就包含了页面间的跳转逻辑。并 且,页面跳转逻辑是定义在代码中的,而不是写在一个配置文件中。页面间的跳转,使用的是IRequ estCycle.activate()方法。下面将会在例子中说明Tapestry是如何去做的。
   让我们来看看holiday booking程序例子:
JSF VS Tapestry 全面比较
   在home page中包含了下面的代码:

代码:
<input jwcid = "new@Submit" listener = "ognl:listeners.newSubmit" name="new_submit" type="submit" value="New" />

   在page类中把submit按钮绑定到了newSubmit()方法。
   在page类中,newSubmit()方法又调用了IRequestCycle's activate()方法来操纵页面跳转逻辑。

代码:
public void newSubmit(IRequestCycle cycle)
{ cycle.activate("NewHoliday") }
 

   Tapestry的这种机制可以用于简单的页面跳转或者表单提交(通过使用DirectLink组件)。同时,也提供了另外一种机制,即在page类中实 现IExternalPage接口,然后使用ExternalLink组件直接跳转到另外一个页面 。
   而对于应用指定的参数,可编码在URL当中,被目标页面以Object数组的形式接收(这比用一个名字/值对的字符串来接收要好)。但是这也意味着你的activateExternalPage()方法的实现必须要以正确的顺序读取这些参数。
   ExternalLink组件可用于书签类型的导航页面,因为所要跳转的目标页面并不依赖于HttpSession的状态。但是在Tapestry中 URL类型的请求格式不是很友好,诸如: http://localhost:8080/tapestry/app?service=external/HolidayDetails&sp=4 这种形式的URL,在Tapestry4.0中将会改善这种形式,增加可读性。
   Tapestry还提供了一种页面转接的形式,可以抛出一个RedirectException,具体情形,可阅读 Redirect After Post 这篇文章。
   JSF
   在JSF中,你必须同时利用代码和配置文件来控制页面跳转流程,为此,你必须做到下面几件事情:
   * 你的表单控制元素(HtmlCommandButton 或者 HtmlCommandLink)必须与managed bean的事件控制单元相绑定。事件控制器(event handler)里面必须有如下形式的定义:

代码:
public String actionName();

   在初始化的时候,为了把事件控制器和动作监听器(action listener)联系起来,在后者的代码里面会有这样一个方法:

代码:
public void actionListenerName(FacesContext context);

   * 在一个action里面返回的字符串实际上就对应 faces-config.xml 里面配置的页面跳转规则,大家来看看下面的配置:

代码:
<navigation-rule>
<from-view-id>/home.jsp</from-view-id>
<navigation-case>
<from-outcome>newHoliday</from-outcome>
<to-view-id>/newholiday.jsp</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>detail</from-outcome>
<to-view-id>/holidaydetail.jsp</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>delete</from-outcome>
<to-view-id>/home.jsp</to-view-id>
</navigation-case>
</navigation-rule>
 

   struts的使用者在这里会发现同struts-config.xml类似的配置方法,在JSF中通过navigation-rule来定义页面跳转。
   * 作为一个捷径,你也可以直接跳转到一个页面。比如:

代码:

<h:commandButton value="next" action="next" /

   在JSF当中,也有通过URL形式做页面跳转的方法。HtmlOutputLink可以用来替换HtmlCommandLink。URL后面的参数被封装 成HttpServletRequest的参数,在目标页面通过FacesContext来接收这 些参数,不过,你需要自己将这个字符串类型的参数转换成对应的数据类型。而这个在Tapestry当中,却是不需要的。
   以本文的观点来看,页面间的跳转实际上也是一个应用的流程控制问题,属于应用本身的逻辑,所以,将这个跳转的控制从应用逻辑中剥离出来,用配置文件的形式来控制,有点过渡设计的意味。
   在URL形式的页面跳转中,Tapestry的IExternalLink机制尽管也不是那么完美,但相比JSF而言,它至少保证了所传递参数的类型安全,并且提供了一个定义良好的机制来解析这些传递的参数。

   以本文的观点来看,更倾向于使用Tapestry的那种在代码中控制页面跳转逻辑的方式,而不是使用一个配置文件。

你可能感兴趣的:(bean,框架,应用服务器,JSF,tapestry)