2006年11月8日

使用 JSF 架构进行设计

探索 JavaServer Faces 框架中使用的设计模式

 
  
 

未显示需要 JavaScript 的文档选项


拓展 Tomcat 应用

下载 IBM 开源 J2EE 应用服务器 WAS CE 新版本 V1.1


级别: 中级

Anand Prakash Joshi (ananjosh@in.ibm.com), 软件工程师, IBM

2006 年 1 月 04 日

本文中,作者 Anand Joshi 使用 JSF 框架中的设计模式阐释了 JavaServer Faces (JSF) 体系结构。他讨论了 JSF 体系结构中使用的 GoF 设计模式,以及这些模式在 JSF 框架中的作用。任何对设计模式和 JSF 体系结构有一定了解的人都能从 Anand 详细的介绍中有所收获。*读者应该对 GoF 设计模式和 JSF 技术有很好的了解。

设计模式可以帮助用户在更高层次上抽象细节,更好地理解体系结构。如果比较熟悉 GoF 设计模式和 JavaServer Faces (JSF) 框架,本文可以帮助您洞察 JSF 框架中使用的设计模式,深入理解其工作原理。

本文探讨了 JSF 框架中使用的设计模式。详细讨论的设计模式包括 Singleton、Model-View-Controller、Factory Method、State、Composite、Decorator、Strategy、Template Method 和 Observer 模式。

设计模式和 JavaServer Faces (JSF) 技术

首先简要地介绍一下模式和 JSF 框架。

  • 模式。设计模式是对问题和解决方案进行抽象的普遍适用的方法。因为模式是所有开发人员和架构师公认的,所以模式可以节约时间和资源。用外行话来说,模式就是关于某个人所共知的问题的经过验证的解决方案。模式可以重用,重用使得解决方案更健壮。
  • Java Server Faces。 JSF 体系结构是一种 Web 应用程序框架。它是 Java Community Process (JCP) 推动的,有望成为 Web 应用程序开发的标准框架。目前用于开发 Web 应用程序的框架有 50 多个,这说明迫切需要实现框架的标准化,这正是 JSF 框架的目标!

 

 
 


深入剖析 JSF 模式

现在我们来讨论 JSF 体系结构中的各种设计模式。本文将详细讨论 Singleton、Model-View-Controller、Factory Method、State、Composite、Decorator、Strategy、Template Method 和 Observer 设计模式。我将分析每种模式的用途及其在 JSF 框架中的作用。



 
 


Singleton 模式

Singleton 模式的目的是保证类只有一个实例被加载,该实例提供一个全局访问点。当启动具有 JSF 支持的 Web 应用程序时,Web 容器初始化一个 FacesServlet 实例。在这个阶段,FacesServlet 对每个 Web 应用程序实例化 Application 和 LifeCycle 实例一次。这些实例就采用众所周知的 Singleton 模式,通常只需要该类型的一个实例。

使用 JSF 的 Web 应用程序只需要 Application 和 LifeCycle 类的一个实例。LifeCycle 管理多个 JSF 请求的整个生命期。因为其状态和行为在所有请求之间共享,这些对象采用 Singleton 模式合情合理。LifeCycle 维护的 PhaseListeners 也是 Singleton 模式的。PhaseListeners 由所有 JSF 请求共享。在 JSF 框架中可以广泛使用 Singleton 模式,以减少内存占用和提供对象的全局访问。NavigationHandler(用于确定请求的逻辑结果)和 ViewHandler(用于创建视图)也是使用 Singleton 模式的例子。




 
 


Model-View-Controller (MVC)

MVC 模式的目的是从数据表示(View)中将数据(即 Model)分离出来。如果应用程序有多种表示,可以仅替换视图层而重用控制器和模型代码。类似的,如果需要改变模型,可以在很大程度上不改变视图层。控制器处理用户动作,用户动作可能造成模型改变和视图更新。当用户请求一个 JSF 页面时,请求发送到 FacesServlet。FacesServlet 是 JSF 使用的前端控制器 servlet。和其他很多 Web 应用程序框架一样,JSF 使用 MVS 模式消除视图和模型之间的耦合。为了集中处理用户请求,控制器 servlet 改变模型并将用户导航到视图。

FacesServlet 是 JSF 框架中所有用户请求都要经过的控制器元素。FacesServlet 分析用户请求,使用托管 bean 对模型调用各种动作。后台(backing)或托管(managed)bean 就是该模型的例子。JSF 用户界面(UI)组件是视图层的例子。MVC 模式把任务分解给具有不同技能的开发人员,使这些任务能够同时进行,这样 GUI 设计人员就可以使用丰富的 UI 组件创建 JSF 页面,同时后端开发人员可以创建托管 bean 来编写专门的业务逻辑代码。




 
 


Factory Method 模式

Factory Method 模式的目的是定义一个用于创建对象的接口,但是把对象实例化推迟到子类中。在 JSF 体系结构中,Factory Method 模式被用于创建对象。LifeCycleFactory 是一个创建和返回 LifeCycle 实例的工厂对象。LifeCycleFactory 的 getLifeCycle (String LifeCycleId) 方法采用 Factory Method 模式,根据 LifeCycleId 创建(如果需要)并返回 LifeCycle 实例。自定义的 JSF 实现可以重新定义 getLifeCycle 抽象方法来创建自定义的 LifeCycle 实例。默认的 JSF 实现提供默认的 LifeCycle 实例。此外,对于每个 JSF 请求,FacesServlet 都从 FacesContextFactory 得到 FacesContext。FacesContextFactory 是一个抽象类,公开了 getFacesContext API,JSF 实现提供了 FacesContextFactory 和 getFacesContext API 的具体实现。这是另外一个使用 Factory Method 模式的例子,具体的 FacesContextFactory 实现创建 FacesContext 对象。




 
 


State 模式

State 模式的目的是在表示状态的不同类之间分配与状态有关的逻辑。FacesServlet 对 LifCycle 实例调用 execute 和 render 方法。LifeCycle 协调不同的 Phrase 以便执行 JSF 请求。在这里 JSF 实现就遵循了 State 模式。如果没有使用这种模式,LifeCycle 实现就会被大量的条件(即 “if” 语句)搅得一塌糊涂。JSF 实现为每个状态(或阶段)创建单独的类并调用 step。phase 是一个抽象类,定了每个 step 的公共接口。在 JSF 框架中定义了六个 phrase(即 step):RestoreViewPhase、ApplyRequestValues、ProcessValidationsPhase、UpdateModelValuesPhase、InvokeApplicationPhase 和 RenderResponsePhase。

在 State 模式中,LifeCycle 把 FacesContext 对象传递给 phase。每个阶段或状态改变传递给它的上下文信息,然后设置 FacesContext 本身中的标志表明下一个可能的步骤。JSF 实现在每个步骤中改变其行为。每个阶段都可以作为下一个阶段的起因。FacesContext 有两种标志 renderResponse 和 responseComplete 可以改变执行的顺序。每个步骤执行完成后,LifeCycle 检查上一阶段是否设置了这些标志。如果设置了 responseComplete,LifeCycle 则完全放弃请求的执行。如果经过某个阶段后设置了 renderResponse 标志,JSF 就会跳过剩下的阶段而直接进入 Render Response 阶段。如果这两个标志都没有设置,LifeCycle 就会按顺序继续执行下一步。




 
 


Composite 模式

Composite 模式让客户代码能够统一处理复合对象和基本对象。复合对象是基本对象的容器。在第一阶段(Restore View 阶段)和最后一个阶段(Render Response 阶段),使用 JSF UI 组件构造 UI View。UIComponentBase 就是 Composite 模式中 Component 抽象类的一个例子。UIViewRoot 是 Composite 类,而 UIOutput(比方说)就是叶子(或者基本类)。UIComponentBase 类定义了叶子和复合对象的公共方法,如编码/解码值和子节点管理函数。子节点管理函数,如 getChildren,对于叶子节点返回空列表,对于复合节点则返回其子节点。




 
 


Decorator 模式

Decorator 模式的目的是不通过子类化动态扩展对象的行为。JSF 框架有很多扩展点(即可插入机制)。JSF 实现可使用 Decorator 模式替换默认的 PropertyResolver、VariableResolver、ActionListener、NavigationHandler、ViewHandler 或 StateManager。通常自定义实现接受通过构造函数传递给它的默认实现的引用。自定义实现仅仅改写功能的一个子集,而将其他功能委托给默认实现。如果希望实现自定义的 ViewHandler,改写默认 ViewHandler 实现的 calculateLocale 方法,可以像 清单 1 那样编写 CustomViewHandler 类:

清单 1. CustomViewHandler 片段

public class CustomViewHandler extends ViewHandler {
 public CustomViewHandler(ViewHandler handler) {
		 super();
		 oldViewHandler = handler;
 }
private ViewHandler oldViewHandler  = null;
public void renderView (facesContext context, UIViewRoot view) {
            //delegate method to oldViewHandler
		 oldViewHandler.renderView(context, view);
}
//custom implementation of calculateLocale
public Locale calculateLocale(FacesContext context) {
}
}




 
 


Strategy 模式

Strategy 模式的目的是封装不同的概念。JSF 框架采用 Strategy 模式使用委托实现模型呈现 UI 组件。JSF 技术支持两种呈现模型。在直接实现模型中,UI 组件对收到的请求中的数据进行解码,然后编码这些数据进行显示。在委托实现模型中,解码和编码操作委托给和组建关联的专门呈现器。后一种模型利用了 Strategy 设计模式,比直接实现更灵活。在 Strategy 模式中,将不同的算法封装在单独的对象中,从而可以动态地改变算法。JSF 实现可以用已有的 renderkit 实例注册另外的呈现器,当应用程序启动的时候,JSF 实现读取配置文件将这些呈现器和 UI 组件联系在一起。




 
 


Template Method 模式

Template Method 模式的目的是将变化的步骤推迟到子类中,而在父类中定义那些固定的算法步骤。JSF 框架通过 PhraseListeners 展现了 Template Method 模式提供的功能。采用 Template Method(或者 “hook”)使得 Web 作者可以为不同阶段之间的可选步骤提供实现,而主要阶段仍然和 JSF 框架的定义一致。JSF 框架提供了 PhaseListeners,概念上类似于 Template Method 模式中的可变步骤。JSF 框架有六个预定义的阶段,在每个阶段之间,Web 作者可以实现 PhaseListeners 来提供类似于 Template Method hook 的 hook。事实上,这种结构比 Template Method 模式更具有扩展性。可以通过注册 PhraseId 为 ANY_PHRASE 的 PhaseListener 在每个阶段后提供 hook。如果 PhaseId 是 ANY_PHASE,JSF 实现就会在每个阶段之前和之后调用该 PhaseListener。JSF 框架中的实现略有不同,因为可以根本没有 PhaseListener,但是在 Template Method 模式中,子类通常重新定义父类中抽象的可变步骤。




 
 


Observer 模式

Observer 模式的目的是当目标对象的状态改变时自动通知所有依赖的对象(即观察器)。JSF 在 UI 组件中实现了 Observer 模式。JSF 有两类内建事件:ActionEvent 和 ValueChangedEvent。ActionEvent 用于确定用户界面组件(如按钮)的激活。当用户单击按钮时,JSF 实现通知添加到该按钮上的一个或多个动作监听程序。于是该按钮被激活,或者说按钮(主体)的状态改变了。添加到按钮上的所有监听程序(即观察器)都收到通知该主体状态已经改变。类似的,当输入 UI 组件中的值改变时,JSF 实现通知 ValueChangeListener。




 
 


结束语

JSF 框架利用了 Singleton、Model-View-Controller、Factory Method、State、Composite、Decorator、Strategy、Template Method 和 Observer 设计模式。因为它的体系结构建立在已经验证的设计模式的基础上,这是一个健壮的框架,模式在 JSF 框架中得到了很好的利用。



参考资料

学习

获得产品和技术

讨论


关于作者

Anand Joshi 的照片

Anand 是一位 Sun 认证的企业架构师,几年来一直研究 Web 技术。他对 WebSphere 管理控制台应用程序的设计和开发做了多方面的贡献。Anand 曾经在 IBM 美国工作过几年,目前在 IBM 印度工作。


posted @ 2006-11-08 09:51 GoodtigerZhao 阅读(302) | 评论 (0)编辑 收藏

2006年11月7日


JSF是一种新的用于构架j2ee应用用户界面的技术,它尤其适合于基于MVC架构的应用中。虽已有很多文章介绍过了JSF,然而它们大多从理论高度来介绍JSF而不是面向于实际应用。目前对于实际应用,JSF仍有很多问题没有解决,例如:如何使JSF适应于MVC整体构架中?如何将JSF与其他Java 框架整合起来?是否应该将业务逻辑放置在JSF的backing beans中?如何处理JSF中的安全机制?更为重要的是如何利用JSF构架现实世界的Web应用?

本文将涉及到上面的这些问题,它将演示如何将JSF、Spring和Hibernate整合在一起,构架出一个名为JCatalog的在线产品价目系统
。利用该Demo,本文涵盖了Web应用开发的每一个阶段,包括需求收集、分析,技术选择,系统架构和实现。本文讨论了在JCatalog中涉及到的各种技术的优点和缺点并展示了一些关键部分的设计方法。

本文的对象是从事基于J2ee的Web应用架构人员和开发人员,它并不是对JSF、SpringFramework和Hibernate的简单介绍,如果对这些领域不甚了解,请参看相关资源。

该范例的功能需求
JCatalog是一个现实世界的Web应用,我首先描述JCatalog的需求,在通篇的技术决策和架构设计时都将涉及到本部分。

在设计Web应用的第一阶段是收集系统的功能需求,范例应用是一个典型的电子商务应用系统,用户可以浏览产品的catalog并查看产品的详细情况,而管理员可以管理产品的catalog。通过增加一些其他功能,如inventory管理和订单处理等,该应用可成为一个成熟的电子商务系统。

Use cases
Use-case分析被用来展示范例应用的功能需求,图1就是该应用的use-case图。
    


use-case图用于表示系统中的actors以及可能进行的operations,在该应用中将有七个use-case,用户能够浏览产品 catalog和查看产品的详细情况,一旦用户登录到系统中,她将成为管理员,从而可以创建新的产品,编辑已存在的产品或者删除老的产品等。

Business rules
JCatalog必须符合以下business rules:
  • 每个产品必须具有唯一的ID
  • 每个产品必须属于至少一个category
  • 产品ID一旦创立不得修改
Assumptions
我们在系统的设计和实现中做以下假定:
  • 英语讲是缺省语言,且不需事先国际化
  • 在Catalog不讲不会超过500个产品
  • catalog将不会被频繁的修改
Page flow
图2显示了所有的JCatalog的pages以及它们之间的transitions关系:
    


该应用中存在两组pages:公开的internet和用于管理的intranet,其中intranet只能被那些成功登录到系统的用户访问。 ProductSummary不作为一个单独的page展示给用户,它显示在Catalog page中的frame中。ProductList只对管理员可视,它包含用于创建、编辑和删除产品的链接。

图3是一个Catalog页面的示意图,理想状况下,在需求文档中应该包含每一页的详细示意图。
        


构架设计
Web应用开发的下一个阶段是构架设计,它包括将应用划分为多个功能组件并将这些组件分割组合成层,高层的构架设计应该中立于所选用的特定技术。

多层架构
多层架构是将整个系统清晰的分为多个功能单元:client、presentation、business-logic、integration和 EIS,这将确保职责得到清晰的划分,使得系统更易于维护和扩展。具有三层或等多层的系统被证明比C/S模型具有更好的伸缩性和灵活性。

client层是使用和表示数据模型的地方,对于一个Web应用,client层通常是浏览器,基于浏览器的瘦客户端不包含任何表示逻辑,它依赖于
presentation 层。

presentation层将business-logic层的服务展示给用户,它应知道如何处理用户的请求,如何同business-logic层交互,并且知道如何选择下一个视图显示给用户。

business-logic层包含应用的business objects和business services。它接受来在于presentation层的请求、基于请求处理业务逻辑。业务逻辑层组件将受益于系统级的服务,如安全管理、事务管理和资源管理等。

integration层是介于
business-logic 层和EIS层之间的桥梁,它封装了与EIS层交互的逻辑。有时,将integration层和business-logic层合称为中间层。

应用的数据被保存在EIS层中,它包括关系数据库、面向对象数据库和以及遗留系统等。

JCatalog的构架设计
图4显示了JCatalog的构架设计以及如何应用于多层构架系统中。
 


该应用采用了多层非分布式的构架,图4展示了系统的分层以及每一层中选择的技术,它同时又是该范例的部署图,它的presentation、 business-logic和integration层将存在于同一个web容器中。定义良好的接口将孤立每一层的职责,这一架构使得应用更为简单和更好的伸缩性。

对于presentation层,经验表明,最好的方法是选择已存在的并已得到证明了的Web应用框架,而不是自己去设计和开发新的框架。我们拥有多个可选择的框架,如Struts,WebWork和JSF等,在JCatalog中,我们选择采用JSF。

EJB和POJO都可以用来创建业务逻辑层,如果应用是分布式的,采用具有remote接口的EJB是一个好的选择;由于JCatalog是一个典型的不需要远程访问的Web应用,因此选用POJO,并充分利用Spring Framework的帮助,将是实现业务逻辑层的更好选择。

integration层利用关系型数据库事先数据的持续化,存在多种方法可用来实现:
  • JDBC:这是最为灵活的方法,然而,低级的JDBC难以使用,而且质量差的JDBC代码很难运转良好
  • Entity beans:CMP的Entity bean是一种分离数据访问代码和处理ORM的昂贵的方法,它是以应用服务器为中心的方法,即entity bean不是将应用与某种数据库类型而是EJB容器约束在一起。
  • O/R mapping framework:一个ORM框架采用以对象为中心的方法实现数据持续化,一个以对象为中心的应用易于开发并具有高度的可移植性。在该领域中存在几个框架可用—JDO、Hibernate、TopLink以及CocoBase等。在我们的范例中将选用Hibernate。
现在,我们将讨论每一层中的设计问题,由于JSF是一个相对较新的技术,因此将着重于它的使用:

presentation 层和JSF
表示层的功能是收集用户的输入、展示数据、控制页面导航并将用户的输入传递给业务逻辑层,表示层同时需要验证用户的输入以及维护应用的session状态。在下面几部分中,我将讨论表示层设计时的考虑和模式,并说明选择JSF作为JCatalog表示层的原因。

MVC
MVC是Java-Blueprints推荐的架构设计模式,MVC将几个方面分离开来,从而减少代码的重复,它以控制为中心并使得应用更具扩展性。MVC同时可帮助具有不同技能的用户更关注于自己的技能,通过定义良好的接口进行相互合作。MVC是表示层的架构设计模式。

JSF
JSF是Web应用的服务器端用户组件框架,它包含以下API:表示UI组件、管理它们的状态、处理事件、服务器端验证、数据转换、定义页面导航、支持国际化,并为这些特性提供扩展能力。它同时包括两个JSP的tag库以在JSP页面中表示UI组件,以及将组件wire为服务器端对象。

JSF和MVC
JSF非常适合于基于MVC的表示层架构,它在行为和表示之间提供了清晰的分离,它使得你可以采用熟悉的UI组件和web层概念而无需受限于某种特殊的脚本技术或标记语言。

JSF backing beans是JSF的Model层,此外,它同样包含actions,action是controller层的扩展,用于将用户的请求委派给业务逻辑层。这里请注意,从整体的应用构架看,业务逻辑层也被称为model层。包含JSF标签的JSP页面是表示层,Faces Servlet提供了controller的功能。

为什么选用JSF?

JSF不仅仅是另外一个Web框架,下面这些特性是JSF区别于其他Web框架之所在:
  • 类Swing的面向对象的Web应用开发:服务器端有状态的UI组件模型,配合event listeners和handlers,促进了面向对象的Web应用开发。
  • backing-bean管理: backing bean是与页面中使用的UI组件相关联的javabean组件,backing-bean管理将UI组件对象的定义同执行应用相关处理和拥有数据的对象分离开来。JSF在合适的范围内保存和管理这些backing-bean实例。
  • 可扩展的UI模型:JSF的UI模型是可配置的、可重用的,用以构建JSF应用的用户界面。你可以通过扩展标准的UI组件来开发出更为复杂的组件,例如菜单条、树组件等。
  • 灵活的rendering模型:renderer分离了UI组件的功能和显示,多个renderers可创建和用来为同一客户端或不同的客户端定义不同的显示。
  • 可扩展的转换和验证模型:基于标准的converter和validator,你可以开发出自己的可提供更好的模型保护的converter和validator。
尽管如此,JSF目前尚未成熟,随同JSF发布的 components、converters和validators都是最基础的,而且per-component验证模型不能处理components 和validators间的many-to-many验证。此外,JSF标签不能与JSTL间无缝的整合在一起。

在下面的章节中,我将讨论几个在JCatalog实现中的关键部分和设计决策。我首先解释managed bean的定义和使用以及JSF中的backing bean,然后,我将说明如何处理安全、分页、caching、file upload、验证以及错误信息定制。

Managed bean,backing bean,view object 和domain object model
JSF中引入了两个新的名词:managed bean和backing bean。JSF提供了一个强大的managed-bean工具,由JSF来管理的JavaBean对象称为managed-bean,一个 managed bean表述了一个bean如何被创建和管理,它不包含该bean的任何功能性描述。

backing bean定义了与页面中使用的UI组件相关联的属性和处理逻辑。每一个backing-bean属性邦定于一个组件实例或某实例的value。一个 backing-bean同时定义了一组执行组件功能的方法,例如验证组件的数据、处理组件触发的事件、实施与组件相关的导航等。

一个典型的JSF应用将其中的每个页面和一个backing-bean结合起来,然而在现实应用中,强制的执行这种one-on-one的关系不是一种理想的解决方案,它可能会导致代码重复等问题。在现实的应用中,多个页面可以共享一个backing-bean,例如在JCatalog中, CreateProduct和EditProduct将共享同一个ProductBean定义。

model对象特定于表示层中的一个view对象,它包含必须显示在view层的数据以及验证用户输入、处理事件和与业务逻辑层交互的处理逻辑等。在基于 JSF的应用中backing bean就是view对象,在本文中backing bean和view对象是可互换的名词。

对比于struts中的ActionForm和Action,利用JSF中的backing-bean进行开发将能更好的遵循面向对象方法,一个 backing-bean不仅包含view数据,而且还包含与这些数据相关的行为,而在struts中,Action和ActionForm分别包含数据和逻辑。

我们都应该听说过domain object model,那么,domain object model和view对象之间有什么区别呢?在一个简单的Web应用中,一个domain object model能够横穿所有层中,而在复杂的应用中,需要用到一个单独的view对象模型。domain object model应该属于业务逻辑层,它包含业务数据和与特定业务对象相关的业务逻辑;一个view对象包含presentation-specific的数据和逻辑。将view对象从domain object model中分离出来的缺点是在这两个对象模型之间必将出现数据映射。在JCatalog中,ProductBeanBuilder和 UserBeanBuilder利用reflection-based Commons BeanUtils来实现数据映射。

安全
目前,JSF没有内建的安全特性,而对于范例应用来说安全需求是非常基础的:用户登录到administration intranet中仅需用户名和密码认证,而无需考虑授权。
针对于JSF的认证,已有几种方法提出:
  • 利用一个backing bean:这一个方法非常简单,然而它却将backing bean与特殊的继承关系结合起来了
  • 利用JSF的ViewHandler decorator:这一方法中,安全逻辑紧密地与一特定Web层技术联系在了一起
  • 利用servlet filter:一个JSF应用与其他的Web应用没有什么两样,filter仍是处理认证检查的最好地方,这种方法中,认证逻辑与Web应用分离开来
在我们的范例程序中,SecurityFilter类被用来处理用户的认证,目前,受保护的资源只包含三个页面,出于简单的考虑,将它们的位置被硬编码到Filter类中。

分页
该应用中的Catalog页面需要分页,表示层可用来处理分页,即它取出所有的数据并保存在这一层;分页同样可在business-logic层、 integration层、甚至EIS层中实现。由于在JCatalog中假设不超过500个产品,因此所有的产品信息能存放在一个user session中,我们将分页逻辑放在了ProductListBean中,与分页相关的参数将通过JSF managed-bean工具配置。

Caching
Caching是提高Web应用性能的最重要技术之一,在应用构建中的很多层中都可以实现caching。JSF managed-bean工具可以使在表示层实现caching非常容易。通过改变一个managed bean的范围,这个managed bean中包含的数据可以在不同的范围内缓存。

范例应用中采用了两级caching,第一级caching存在于业务逻辑层,CachedCatalogServiceImpl类维护了一个所有产品和目录的读写cache,Spring将该类作为一个singleton service bean来管理,所以,一级cache是一个应用范围的读写cache。

为了简化分页逻辑并进而提高应用的速度,产品同样在session范围内缓存到表示层,每一个用户维护着他自己的ProductListBean,这一方法的缺点是内存的消耗和数据的失效问题,在一个用户session中,如果管理员更改了catalog,用户可到的将是失效的数据,然而,由于我们假设应用的数据不会经常的改变,所以这些缺点将能够忍受。

File upload
目前的JSF Sun参考实现中不支持file upload。Struts虽已具有非常不错的file upload能力,然而要想使用这一特性需要Struts-Faces整合库。在JCatalog中,一个图像与一个产品相关联,在一个用户创建了新的产品后,她必须将相应的图片上传,图片将保存在应用服务器的文件系统里,产品的ID就是图像名称。

范例应用中采用、Servlet和Jakarta Common的file-upload API来实现简单的文件上传功能,该方法包含两个参数:图像路径和图像上传结果页面。它们将通过ApplicationBean来配置,详细内容请参看 FileUploadServlet类。

Validation
JSF中发布的标准validator是非常基础的,无法满足现实的需要,但很容易开发出自己的JSF validator,在范例中,我开发了SelectedItemsRange validator,它用来验证UISelectMany组件中选择的数量:

 
    
    
 


详细情况请参看范例。

定制错误信息
在JSF中,你可以为converters和validators创建resource bundle和定制错误信息,一个resource bundle可在faces-config.xml中创建:

  catalog.view.bundle.Messages

并将错误信息的key-value对加到Message.properties文件中:

  javax.faces.component.UIInput.CONVERSION=Input data is not in the correct type.
  javax.faces.component.UIInput.REQUIRED=Required value is missing.

业务逻辑层和Spring Framework
业务对象和业务服务存在于业务逻辑层中,一个业务对象不仅包含数据,而且包含相应的逻辑,在范例应用中包含三个业务对象:Product、Category和User。

业务服务与业务对象交互并提供更高级的业务逻辑,需要首先定义一个正式的业务接口,它是直接与终端用户交互的服务接口。在JCatalog中,通过在 Spring Framework帮助下的POJO实现业务逻辑层,其中共有两个业务服务:CatalogService包含Catalog管理相关的业务逻辑, UserService中包含User管理逻辑。

Spring是基于IoC概念的框架,在范例应用中用到的Spring特性包括:
  • Bean management with application contexts:Spring可以有效地组织我们的中间层对象,它能够消除singleton的proliferation,并易于实现良好的面向对象编程方法,即“编程到接口”。
  • Declarative Transaction management: Spring利用AOP实现事务管理,而无需借助于EJB容器,利用这种方法,事务管理可以用于任何POJO中。Spring的事务管理不局限于JTA,而是可以采用不同的事务策略,在范例应用中,我们将使用declarative transaction management with Hibernate transaction。
  • Data-access exception hierarchy:Spring提供了非常好的异常来代替SQLException,为利用Spring的异常,必须在Spring的配置文件中定义以下异常转换:
       
         
             
         

       


        在范例应用中,如果一个具有重复ID的新产品被插入,将会抛出DataIntegrityViolationException,这一异常将被
        catch并rethrown一个DuplicateProductIdException。这样,该异常就可以与其它的异常区别处理。
  • Hibernate integration:Spring与Hibernate这样的ORM框架整合的非常好,Spring提供了对Hibernate session的高效和安全的处理,它可通过application context配置Hibernate的SessionFactories和JDBC数据源,并使得应用易于测试。

Integration层和Hibernate
Hibernate是一个开源的ORM框架,它可以支持所有主流SQL数据库系统,Hibernate的查询语言为对象和关系架起了非常好的桥梁。Hibernate提供了强大的功能以实现:数据读取和更新、事务管理、数据连接池、查询和实体关系管理等。

Data Access Ojbect(DAO)
JCatalog中采用了Dao模式,该模式抽象和封装了所有对数据源的访问,该应用中包括两个DAO接口:CatalogDao和UserDao,它们相应的实现HibernateCatalogDaoImpl和HibernateUserDAoImpl包含了Hibernate特定的逻辑来实现数据的管理和持久化。

实现
现在我们来看看如何将上面讨论的这些东西包装在一起以实现JCatalog,你可以从这个地址下载源码:source code

数据库设计
我们为该范例应用创建了包含4个表的数据库,如图5所示:


类设计
图6显示了JCatalog的类图


“编程到接口”的思想贯穿了整个设计实现中,在表示层,共用到四个backing bean:ProductBean、ProductListBean、UserBean和MessageBean;业务逻辑层包含两个业务服务 (CatalogService和UserService)和三个业务对象(Product、Category和User);Integration层有两个Dao接口和它们相应的Hibernate实现,Spring的application context用来管理绝大多数的业务逻辑层和integration层的对象;ServiceLocator将JSF和业务逻辑层整合在了一起。

Wire everything up
由于篇幅所限,我们仅举例说明,范例中use case CreateProduct展示了如何装配和构建应用,在详细讲述细节前,我们利用sequence图(图7)来说明所有层的end-tp-end整合。



表示层
表示层实现包括创建JSP页面、定义页导航、创建和配置backing bean以及将JSF与业务逻辑层整合。
  • JSP page:createProduct.jsp是用来创建新产品的页面,它包含UI组件并将组件打包成ProductBean,ValidateItemsRange标签用来验证用户选择的种类数量,对每一个产品至少要有一个种类被选中。
  • 页面导航:应用中的导航被定义在应用的配置文件faces-navigation.xml中,CreateProduct的导航准则如下:

   *
  
      createProduct
      /createProduct.jsp
  


   /createProduct.jsp
  
      success
      /uploadImage.jsp
  
  
      retry
      /createProduct.jsp
  
  
      cancel
      /productList.jsp
  

  • Backing bean:ProductBean不仅包含有将数据映射到页面上的UI组件的属性,还包括三个action:createAction、editAction和deleteAction,下面是createAction方法的代码:
public String createAction() {
   try {
      Product product = ProductBeanBuilder.createProduct(this);

      //Save the product.
      this.serviceLocator.getCatalogService().saveProduct(product);

      //Store the current product id inside the session bean.
      //For the use of image uploader.
      FacesUtils.getSessionBean().setCurrentProductId(this.id);

      //Remove the productList inside the cache.
      this.logger.debug("remove ProductListBean from cache");
      FacesUtils.resetManagedBean(BeanNames.PRODUCT_LIST_BEAN);
   } catch (DuplicateProductIdException de) {
      String msg = "Product id already exists";
      this.logger.info(msg);
      FacesUtils.addErrorMessage(msg);

      return NavigationResults.RETRY;
   } catch (Exception e) {
      String msg = "Could not save product";
      this.logger.error(msg, e);
      FacesUtils.addErrorMessage(msg + ": Internal Error");

      return NavigationResults.FAILURE;
   }
   String msg = "Product with id of " + this.id + " was created successfully.";
   this.logger.debug(msg);
   FacesUtils.addInfoMessage(msg);

   return NavigationResults.SUCCESS;
}
  • Managed-bean声明:ProductBean必须在JSF配置文件faces-managed-bean.xml中配置:

  
      Backing bean that contains product information.
  
   productBean
   catalog.view.bean.ProductBean
   request   
  
      id
      #{param.productId}
  
  
      serviceLocator
      #{serviceLocatorBean}
  

  •  表示层和业务逻辑层之间的整合: ServiceLocator抽象了查找服务的逻辑,在范例应用中,ServiceLocator被定义为一个接口,该接口实现为一个JSF的 managed bean,即ServiceLocatorBean,它将在Spring的application context中寻找服务:
ServletContext context = FacesUtils.getServletContext();
this.appContext = WebApplicationContextUtils.getRequiredWebApplicationContext(context);
this.catalogService = (CatalogService)this.lookupService(CATALOG_SERVICE_BEAN_NAME);
this.userService = (UserService)this.lookupService(USER_SERVICE_BEAN_NAME);
业务逻辑层
  • 业务对象:由于采用Hibernate提供持久化,因此Product和Category两个业务对象需要为它们的所有field提供getter和setter。
  • 业务服务:CatalogService接口中定义了所有的与Catalog management相关的服务:
public interface CatalogService {
   public Product saveProduct(Product product) throws CatalogException;
   public void updateProduct(Product product) throws CatalogException;
   public void deleteProduct(Product product) throws CatalogException;
   public Product getProduct(String productId) throws CatalogException;
   public Category getCategory(String categoryId) throws CatalogException;
   public List getAllProducts() throws CatalogException;
   public List getAllCategories() throws CatalogException;
}
  • Spring Configuration:这里是CatalogService的Spring配置:


  




  




  
  
  
     
         PROPAGATION_REQUIRED,readOnly
       PROPAGATION_REQUIRED
       PROPAGATION_REQUIRED
       PROPAGATION_REQUIRED
     
  

  • Spring和Hibernate的整合:下面是HibernateSessionFactory的配置:

<!-- Hibernate SessionFactory Definition -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
   <property name="mappingResources">
      <list>
         <value>catalog/model/businessobject/Product.hbm.xml</value>
         <value>catalog/model/businessobject/Category.hbm.xml</value>
         <value>catalog/model/businessobject/User.hbm.xml</value>
      </list>
   </property>
   <property name="hibernateProperties">
      <props>
         <prop key="hibernate.dialect">net.sf.hibernate.dialect.MySQLDialect</prop>
       <prop key="hibernate.show_sql">true</prop>
       <prop key="hibernate.cglib.use_reflection_optimizer">true</prop>
       <prop key="hibernate.cache.provider_class">net.sf.hibernate.cache.HashtableCacheProvider</prop>
      </props>
   </property>
   <property name="dataSource">
      <ref bean="dataSource"/>
   </property>
</bean>

CatalogDao uses HibernateTemplate to integrate between Hibernate and Spring. Here's the configuration for HibernateTemplate:

<!-- Hibernate Template Defintion -->
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate.HibernateTemplate">
   <property name="sessionFactory"><ref bean="sessionFactory"/></property>
   <property name="jdbcExceptionTranslator"><ref bean="jdbcExceptionTranslator"/></property>
</bean>



Integration层
Hibernate通过xml配置文件来映射业务对象和关系数据库,在JCatalog中,Product.hbm.xml表示了Product对象的映射,Category.hbm.xml则用来表示Category的映射,Product.hbm.xml如下:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
      "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
      "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping package="catalog.model.businessobject">
   <class name="Product" table="product">
      <id name="id" column="ID" unsaved-value="null">
         <generator class="assigned"/>
      </id>
      <property name="name" column="NAME" unique="true" not-null="true"/>
      <property name="price" column="PRICE"/>    
      <property name="width" column="WIDTH"/>      
      <property name="height" column="height"/>      
      <property name="description" column="description"/>   
      <set name="categoryIds" table="product_category" cascade="all">
         <key column="PRODUCT_ID"/>
         <element column="CATEGORY_ID" type="string"/>
      </set>
   </class>
</hibernate-mapping>

CatalogDao is wired with HibernateTemplate by Spring:

<!-- Catalog DAO Definition: Hibernate implementation -->
<bean id="catalogDao" class="catalog.model.dao.hibernate.CatalogDaoHibernateImpl">
   <property name="hibernateTemplate"><ref bean="hibernateTemplate"/></property>
</bean>

结论
本文主要讲述了如何将JSF与Spring、Hibernate整合在一起来构建实际的Web应用,这三种技术的组合提供了一个强大的Web应用开发框架。在Web应用的高层设计中应该采用多层构架体系,JSF非常适合MVC设计模式以实现表示层,Spring可用在业务逻辑层中管理业务对象,并提供事物管理和资源管理等,Spring与Hibernate结合的非常出色,Hibernate是强大的O/R映射框架,它可以在integration层中提供最好的服务。

通过将整个Web应用分割成多层,并借助于“编程到接口”,应用程序的每一层所采用的技术都是可替换的,例如Struts可以用来替换JSF,JDO可替换Hibernate。各层之间的整合不是不值得研究,采用IoC和ServiceLocator设计模式可使得整合非常容易。JSF提供了其它Web框架欠缺的功能,然而,这并不意味着你马上抛弃Struts而开始使用JSF,是否采用JSF取决于项目目前的状况和功能需求,以及开发团队的意见等。
posted @ 2006-11-07 19:46 GoodtigerZhao 阅读(276) | 评论 (0)编辑 收藏
 

板桥里人 http://www.jdon.com 2005/09/05

  Struts和JSF/Tapestry都属于表现层框架,这两种分属不同性质的框架,后者是一种事件驱动型的组件模型,而Struts只是单纯的MVC模式框架,老外总是急吼吼说事件驱动型就比MVC模式框架好,何以见得,我们下面进行详细分析比较一下到底是怎么回事?

  首先事件是指从客户端页面(浏览器)由用户操作触发的事件,Struts使用Action来接受浏览器表单提交的事件,这里使用了Command模式,每个继承Action的子类都必须实现一个方法execute。

  在struts中,实际是一个表单Form对应一个Action类(或DispatchAction),换一句话说:在Struts中实际是一个表单只能对应一个事件,struts这种事件方式称为application event,application event和component event相比是一种粗粒度的事件。

  struts重要的表单对象ActionForm是一种对象,它代表了一种应用,这个对象中至少包含几个字段,这些字段是Jsp页面表单中的input字段,因为一个表单对应一个事件,所以,当我们需要将事件粒度细化到表单中这些字段时,也就是说,一个字段对应一个事件时,单纯使用Struts就不太可能,当然通过结合JavaScript也是可以转弯实现的。

  而这种情况使用JSF就可以方便实现,

<h:inputText id="userId" value="#{login.userId}">
  <f:valueChangeListener type="logindemo.UserLoginChanged" />
</h:inputText>

  #{login.userId}表示从名为login的JavaBean的getUserId获得的结果,这个功能使用struts也可以实现,name="login" property="userId"

  关键是第二行,这里表示如果userId的值改变并且确定提交后,将触发调用类UserLoginChanged的processValueChanged(...)方法。

  JSF可以为组件提供两种事件:Value Changed和 Action. 前者我们已经在上节见识过用处,后者就相当于struts中表单提交Action机制,它的JSF写法如下:

<h:commandButton id="login" commandName="login">
  <f:actionListener type=”logindemo.LoginActionListener” />
</h:commandButton>

  从代码可以看出,这两种事件是通过Listerner这样观察者模式贴在具体组件字段上的,而Struts此类事件是原始的一种表单提交Submit触发机制。如果说前者比较语言化(编程语言习惯做法类似Swing编程);后者是属于WEB化,因为它是来自Html表单,如果你起步是从Perl/PHP开始,反而容易接受Struts这种风格。

基本配置

  Struts和JSF都是一种框架,JSF必须需要两种包JSF核心包、JSTL包(标签库),此外,JSF还将使用到Apache项目的一些commons包,这些Apache包只要部署在你的服务器中既可。

  JSF包下载地址:http://java.sun.com/j2ee/javaserverfaces/download.html选择其中Reference Implementation。

  JSTL包下载在http://jakarta.apache.org/site/downloads/downloads_taglibs-standard.cgi

  所以,从JSF的驱动包组成看,其开源基因也占据很大的比重,JSF是一个SUN伙伴们工业标准和开源之间的一个混血儿。

  上述两个地址下载的jar合并在一起就是JSF所需要的全部驱动包了。与Struts的驱动包一样,这些驱动包必须位于Web项目的WEB-INF/lib,和Struts一样的是也必须在web.xml中有如下配置:

<web-app>
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.faces</url-pattern>
  </servlet-mapping>
</web-app>

  这里和Struts的web.xml配置何其相似,简直一模一样。

  正如Struts的struts-config.xml一样,JSF也有类似的faces-config.xml配置文件:


<faces-config>
  <navigation-rule>
    <from-view-id>/index.jsp</from-view-id>
    <navigation-case>
      <from-outcome>login</from-outcome>
      <to-view-id>/welcome.jsp</to-view-id>
    </navigation-case>
  </navigation-rule>

  <managed-bean>
    <managed-bean-name>user</managed-bean-name>
    <managed-bean-class>com.corejsf.UserBean</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
  </managed-bean>
</faces-config>

 

  在Struts-config.xml中有ActionForm Action以及Jsp之间的流程关系,在faces-config.xml中,也有这样的流程,我们具体解释一下Navigation:

  在index.jsp中有一个事件:

<h:commandButton label="Login" action="login" />

  action的值必须匹配form-outcome值,上述Navigation配置表示:如果在index.jsp中有一个login事件,那么事件触发后下一个页面将是welcome.jsp

  JSF有一个独立的事件发生和页面导航的流程安排,这个思路比struts要非常清晰。

  managed-bean类似Struts的ActionForm,正如可以在struts-config.xml中定义ActionForm的scope一样,这里也定义了managed-bean的scope为session。

  但是如果你只以为JSF的managed-bean就这点功能就错了,JSF融入了新的Ioc模式/依赖性注射等技术。

Ioc模式

  对于Userbean这样一个managed-bean,其代码如下:

public class UserBean {
  private String name;
  private String password;

  // PROPERTY: name
  public String getName() { return name; }
  public void setName(String newValue) { name = newValue; }

  // PROPERTY: password
  public String getPassword() { return password; }
  public void setPassword(String newValue) { password = newValue; }
}

<managed-bean>
  <managed-bean-name>user</managed-bean-name>
  <managed-bean-class>com.corejsf.UserBean</managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>

  <managed-property>
    <property-name>name</property-name>
    <value>me</value>
  </managed-property>

  <managed-property>
    <property-name>password</property-name>
    <value>secret</value>
  </managed-property>
</managed-bean>

  faces-config.xml这段配置其实是将"me"赋值给name,将secret赋值给password,这是采取Ioc模式中的Setter注射方式

Backing Beans

  对于一个web form,我们可以使用一个bean包含其涉及的所有组件,这个bean就称为Backing Bean, Backing Bean的优点是:一个单个类可以封装相关一系列功能的数据和逻辑。

  说白了,就是一个Javabean里包含其他Javabean,互相调用,属于Facade模式或Adapter模式。


  对于一个Backing Beans来说,其中包含了几个managed-bean,managed-bean一定是有scope的,那么这其中的几个managed-beans如何配置它们的scope呢?

<managed-bean>
  ...
  <managed-property>
    <property-name>visit</property-name>
    <value>#{sessionScope.visit}</value>
  </managed-property>

  这里配置了一个Backing Beans中有一个setVisit方法,将这个visit赋值为session中的visit,这样以后在程序中我们只管访问visit对象,从中获取我们希望的数据(如用户登陆注册信息),而visit是保存在session还是application或request只需要配置既可。

UI界面

  JSF和Struts一样,除了JavaBeans类之外,还有页面表现元素,都是是使用标签完成的,Struts也提供了struts-faces.tld标签库向JSF过渡。

  使用Struts标签库编程复杂页面时,一个最大问题是会大量使用logic标签,这个logic如同if语句,一旦写起来,搞的JSP页面象俄罗斯方块一样,但是使用JSF标签就简洁优美:

<jia:navigatorItem name="inbox" label="InBox"
  icon="/images/inbox.gif"
  action="inbox"
  disabled="#{!authenticationBean.inboxAuthorized}"/>

  如果authenticationBean中inboxAuthorized返回是假,那么这一行标签就不用显示,多干净利索!

  先写到这里,我会继续对JSF深入比较下去,如果研究过Jdon框架的人,可能会发现,Jdon框架的jdonframework.xml中service配置和managed-bean一样都使用了依赖注射,看来对Javabean的依赖注射已经迅速地成为一种新技术象征,如果你还不了解Ioc模式,赶紧补课。

附Jsf核心教程一个JSF案例:login.rar

相关讨论:

表现层框架Struts/Tapestry/JSF架构比较

讨论

posted @ 2006-11-07 19:38 GoodtigerZhao 阅读(264) | 评论 (0)编辑 收藏
仅列出标题