集成Struts、Tiles和JavaServerFaces

779 篇文章 0 订阅
<script type="text/javascript"> </script> <script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>
<script type="text/javascript"> </script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>

  将三种技术的功能、灵活性和可管理性集成到一起

  级别:高级

  SrikanthShenoy(srikanth@srikanth.org),J2EE顾问,ObjectseekInc.

  NithinMallya(nithin@mallya.org),J2EE顾问,ObjectseekInc.

  2003年10月

  您是否想将JavaServerFaces(JSF)的强大前端功能、Tiles的内容格式编排优势和Strutscontroller层的灵活性都加入到您的J2EEWeb应用程序中?企业级Java专家SrikanthShenoy和NithinMallya为您展示了如何将这三者的功能集成到一起。本文演示了如何在Struts-Faces集成库中定制类以使得它们可以与Tiles和JSF一同使用,并用一个实际的例子解释了这个过程背后的基本原理以及如何使用新的一组类的细节。

  将StrutsTiles和JavaServerFaces(JSF)一起使用,开发人员可以实现易于管理和重用的、健壮的、界面清晰的Web应用程序。

  Struts框架推出已经有一段时间了,它已经成为在开发J2EEWeb应用程序时开发人员所采用的事实上的标准。Tiles框架是在Struts之后不久出现的,它通过为开发人员提供用组件组装展示页面的能力开拓了自己的生存环境。JSF是Web应用程序框架中最新的成员,它提供了验证用户输入和处理用户事件的机制,最重要的是,这是一种以协议无关的方式呈现用户界面组件的方法(有关这些技术的概况,参见本文相关页面“Themajorplayers”)。

  尽管Struts和JSF中有一些功能是重叠的,但是它们在其他方面起到了互为补充的作用。这三种技术的结合可以为开发Web应用程序、组织其展示和以协议无关的方式呈现定制的用户界面(UI)组件提供一种高效的途径。

  为了运行本文中的示例代码,需要Struts1.1、Tiles、JavaServerFacesReferenceImplementation(JSF-RI)EarlyAccessRelease4.0以及Struts-Faces0.4。Jakarta项目提供的Struts1.1发行版本将StrutsTiles捆绑发布。还可以从Jakarta项目上下载Struts-Faces集成库。JSF-RI是Sun的Web开发工具包(WebServicesDeveloperPack)的一部分(在参考资料中有这些下载和示例代码的链接)。

  现在回到集成三种技术的细节上。首先有个坏消息:在本文发表的时候,这三种技术是不能直接互操作的。好消息是:在本文中,我们展示了集成StrutsTiles和JSF的方法。我们假设您已经了解StrutsTiles。对JSF有一些了解会有帮助(参阅参考资料中提供的developerWorks上的JSF教程的链接),但是不了解也不妨碍对本文的理解。

  JSF简介

  JSF应用程序是使用JSF框架的普通J2EEWeb应用程序,JSF框架提供了丰富的GUI组件模型,这些模型体现了真正的GUI框架内涵。您可能听人们说过,尽管某种技术不错,但是它的外观仍然需要改进。是的,用HTML组件构建平淡无奇的页面的日子已经过去了,如果使用JSF的话,具有更高级GUI外观的日子就在眼前。您会问,怎么做呢?树形组件、菜单组件和图形是已经存在的UI组件,这些JSF一定要提供。更进一步,JSF通过提供容易使用的API鼓励创建自定义组件。

  注:这里所提到的UI组件是Sun提供的示例的一部分。像所有规范一样,实际的实现由不同的提供商完成。

  在传统的使用模型-视图-控制器(MVC)的Web应用程序中,GUI组件是由处理展示和业务逻辑的自定义标记所表示的。这样就出现了必须“编写与客户机设备打交道的代码”的问题,这会产生重复的代码。使用JSF就不会有这个问题。

  JSF结构将展示逻辑(“什么”)与UI组件的业务逻辑(“为什么”和“如何”)分离。通过在JSP页面中使用JSF标记,就可以将renderer与UI组件关联在一起。一个UI组件可以用不同的renderer从而以不同的方式呈现。特定于UI组件的代码在服务器上运行,并且响应用户操作所产生的事件。

  JSF-RI提供了一个renderkit,它带有一个自定义标记库,用以从UI组件呈现HTML。它还提供了根据需要定制这些组件外观的能力。如果需要特殊的组件,那么可以为特定的客户机设备构造定制的标记并让它与一个子UI组件和定制的renderer相关联。对于不同的设备,您所需要做的就是指定不同的renderer。

  JSF和UI组件

  您可能已经用JavaAWT或者SwingAPI创建过JavaGUI应用程序,所以您应该熟悉JSF的UIComponent(它与AWT或者Swing组件很相像)。它储存其子组件的树(如果有的话)并为客户端发生的动作生成标准事件,例如单击一个按钮以提交表单。这些事件缓存在FacesContext中。您可以用自定义标记关联每一个这种事件的处理程序。例如,用一个自定义的ActionListener处理用户单击或者表单提交。

  JSFUIComponent、Renderer和标记总是共同工作的。所有JSP自定义标记都是通过继承UIComponentTag创建的。doStart和doEnd方法总是在UIComponentTag类中实现。您只需在这些标记类中提供其他的功能。

  图1展示了自定义标记、UI组件和renderer之间的关系。客户机浏览器访问用JSF标记(jsf:myTag)表示UI组件(MyComponent)的JSP页面。这个UI组件运行在服务器上,并用适当的renderer(MyRenderer)以HTML的形式呈现给客户。这个JSP页面表现了在JSF-RI中使用带自定义标记的用户界面组件而不是在HTML中对它们进行编码。

  例如,图1展示了h:panel:group标记的使用。这个标记用于将一个父组件下面的各个组件组织到一起。如果与像panel_grid和panel_data这样的其他面板标记共同使用,那么它会在运行时生成HTML表中的列的标记。JSF-RI-提供的html_basic标记库用于表示像文本字段、按钮这样的HTML组件。

  图1.呈现一个JSF页面

  JSF生命周期

  JSF生命周期包括六个阶段:一个传入的请求可能会经历全部阶段,也可能不经历任何阶段,这取决于请求的类型、在生命周期中发生的验证和转换错误以及响应的类型。JSF框架处理由JSP页生成的Faces请求,并返回faces或者non-faces响应。

  在提交一个JSF表单,或者当用户单击指向在URL中具有/faces前缀的URL的链接时,就会出现faces响应。所有faces请求都由一个FacesServlet处理--这是JSF中的控制器。

  发送给一个servlet或者一个没有JSF组件的JSP页面的请求称为non-faces请求。如果结果页中有JSF标记,那么它就称为faces响应,如果没有JSF标记,就是non-faces响应。

  JSF生命周期有六个阶段:

  重建请求树

  应用请求值

  进行验证

  更新模型值

  调用应用程序

  呈现响应

  根据JSF规范,每一阶段表示请求处理生命周期的一个逻辑概念。不过在JSF-RI中,这些阶段是由具有对应名字的实际类表示的。下面一节描述了每一阶段是如何对请求进行处理并生成响应的。您将首先看到的是处理一个faces请求所涉及的阶段,然后是处理faces响应所涉及的阶段。

  处理faces请求

  为了理解JSF请求处理,请看FlightSearch.jsp,这是清单1中的一个简单的JSF表单。一个JSF页面基本上就是这个样子的。这个JSF表单有输入文本字段from和tocities、departure和returndates,还有提交和重设表单的按钮(我们会在稍后分析清单1中每一个标记的意义)。现在,假设提交这个表单产生了一个faces请求。

  这个请求被FacesServlet所接收、并在向客户发回响应之前通过不同的阶段。图2展示了如何对JSF请求进行处理。让我们看一看这是如何进行的。

  1.接收请求

  FacesServlet接收请求并从FacesContextFactory得到FacesContext的一个实例。

  2.委托生命周期处理

  FacesServlet通过对在faces上下文中传递的Lifecycle实现调用execute方法将生命周期处理委托给Lifecycle接口。

  3.Lifecycle执行每一阶段

  Lifecycle实现执行从重建组件树阶段开始的每一阶段。

  4.创建的组件树

  在重建组件树阶段,用travelForm中的组件创建一个组件树。这个树以UIForm作为根,用不同的文本字段和按钮作为其子组件。

  fromCity字段有一个验证规则,它规定其不能为空,如validate_required标记所示。这个标记将fromCity文本字段与一个JSFValidator链接起来。

  JSF有几个内建的验证器。相应的Validator是在这个阶段初始化的。这个组件树缓存在FacesContext中、并且这个上下文会在后面用于访问树及调用任何一个事件处理程序。同时UIForm状态会自动保存。所以,当刷新这一页时,就会显示表单的原始内容。

  5.从树中提取值

  在应用请求值阶段,JSF实现遍历组件树并用decode方法从请求中提取值,并在本地设置每一个组件。如果在这个过程中出现了任何错误,那么它们就在FacesContext中排队并在呈现响应阶段显示给用户。

  同时,在这个阶段排队的所有由像单击按钮这样的用户操作产生的事件,都广播给注册的侦听器。单击reset按钮会将文本字段中的值重新设置为它们原来的值。

  6.处理验证

  在处理验证阶段,对在应用请求值阶段设置的本地值进行所有与各组件相关的验证。当JSF实现对每一个注册的验证器调用validate方法时就会进入此阶段。

  如果任何一项验证失败,那么生命周期就会进入呈现响应阶段,在那里呈现带有错误信息的同一页面。在这里,所有在这一阶段排队的事件同样都会广播给注册的侦听器。

  JSF实现处理源字段上的验证器。如果数据是无效的,那么控制就交给呈现响应阶段,在这个阶段重新显示FlightSearch.jsp并带有相关组件的验证错误。通过在JSP页面中声明output_errors,页面中的所有错误都会显示在页面的底部。

  7.设置模型对象值

  在更新模型值阶段,成功处理了所有验证后,JSF实现就通过对每一组件调用updateModel方法用有效值设置模型对象值。如果在将本地数据转换为由模型对象属性所指定的类型时出现任何错误,那么生命周期就进入呈现响应阶段,并将错误显示出来。来自表单字段属性的值会填充为模型对象的属性值。

  8.可以调用ActionListener

  可以将一个ActionListener与一个用户操作,如单击提交按钮相关联,如清单1所示。在调用应用程序阶段,对FlightSearchActionListener调用了processAction方法。在实际应用中,processAction方法在调用后会搜索数据以找出满足条件的航班,并从组件的action属性中提取输出。

  在本文提供的这个示例Web应用程序中,我们使用了静态数据表示航班表。这个方法还将提取的action属性发送给NavigationHandler实现。NavigationHandler查询faces-config.xml文件--这是JSF的默认应用程序配置文件--以根据这一输出确定下一页是什么。

  9.呈现响应

  在呈现响应阶段,如果在faces上下文中没有错误,就显示由查询配置文件得到的这一页FlightList.jsp。如果是因为前面任一阶段的错误而到达这一阶段的,那么就会重新显示带有错误信息的FlightSearch.jsp。

  图2.处理一个JSF请求

  单击这里以观看该图。

  清单1.FlightSearch.jsp,一个简单的JSF表单

  <%@tagliburi="http://java.sun.com/jsf/html"prefix="h"%>

  <%@tagliburi="http://java.sun.com/jsf/core"prefix="f"%>

 

 

 

 

 

 

 

  valueRef="FlightSearchBean.departureDate">

 

  valueRef="FlightSearchBean.arrivalDate">

 

  label="Submit"commandName="submit">

 

  type="foo.bar.FlightSearchActionListener"/>

 

 

  commandName="reset"/>

 

 

 

  在这段代码中使用了两个JSF-RI的标记库。html_basic标记库定义了HTML组件常用的标记,而jsf-core标记库包含用于注册侦听器和验证器的标记。其他标记有:

  f:use_faces标记向JSF实现表明后面的标记是faces标记。

  f:validate_required标记表明它所在的字段(在FlightSearchBean中是fromCity字段)在提交表单时应该有值。

  h:form和h:input_text标记分别表示一个名为flightSearchForm的HTML表单和各种文本字段。

  h:command_button标记用于表示提交和重设按钮。

  最后,h:output_errors标记类似于Strutshtml:errors标记,用于显示在表单字段验证中出现的任何错误。

  一个名为FlightSearchBean的JavaBean表示在更新模型值阶段用UIComponent数据更新的模型。通常在JSP页中JavaBean是用jsp:useBean标记声明的。您可能注意到了在FlightSearch.jsp中没有这样做。这是因为可以使用JSF的一个名为ManagedBeans的功能,在faces配置文件中声明所有JSP页面使用的JavaBeans组件。在开始时,servlet容器会初始化这些JavaBeans组件。faces-config.xml文件中的FlightSearchBean入口如清单2所示:

  清单2.faces-config.xml的TravelInfoBean入口

 

  FlightSearchBean

 

  foo.bar.FlightSearchBean

 

  session

 

  现在让我们看一看这些阶段是如何处理响应的。

  呈现faces响应

  一个faces响应是由Faces应用程序在生成包含JSF标记的JSP页时生成的。这个响应可以是JSF应用程序的faces或者non-faces响应。

  在我们的例子中,清单1中页面的呈现是一个faces响应。您可能熟悉Tag接口的doStartTag()和doEndTag()方法。在JSF和Struts-Faces中,每一个标记都是从UIComponentTag扩展的。UIComponentTag实现了doStartTag()和doEndTag()方法。

  它还有两个抽象方法getComponentType()和getRendererType()。通过在具体的标记类中实现这两个方法,就可以分别指定组件和renderer的类型。

  考虑一个带有文本字段的简单JSF表单。在呈现JSF表单时执行以下一系列步骤。

  1.调用doStartTag()方法

  Servlet窗口对FormTag调用doStartTag()方法。

  2.得到UIComponent

  FormTag从getComponentType()方法得到其UIComponent。UIComponentTag(FormTag的父组件)使用getComponentType()以从faces-config.xml文件中查询这个组件的类名,并创建UIComponent(FormComponent)的一个实例。

  3.得到renderer

  下一步,FormTag从getRendererType方法中得到其renderer。与组件类型一样,renderer名是在faces-config.xml文件中查询的。

  4.调用编码方法

  在创建了FormComponent和FormRenderer后,对FormComponent调用encodeBegin()方法。每一个标记的呈现都由encodeBegin()开始、由encodeEnd()结束。encodeBegin()方法是按嵌套的顺序调用的。

  5.结束标记和呈现HTML

  servlet容器对标记调用doEndTag()方法。以嵌套的反顺序对每一个组件调用encodeEnd()方法。在最后,表单和所有嵌套的组件都呈现为HTML。这时,HTML就生成完毕,并呈现出对应于JSP的HTML。

  图3显示构成生成faces响应的事件序列。

  图3.呈现一个faces响应

  单击这里以查看该图。

  为什么将这三者集成为一体?

  随着JSP和相关规范的不断发展,像JSF和JSP标记库(或者JSTL,它使用简单的标记封装许多JSP应用程序常用的核心功能)这样的新标准正在不断出现。下面是使用集成为一个整体的新技术一些好处:

  更清晰地分离行为和展示。将标记、renderer和组件分离,就可以更好地定义开发周期中的页面作者和应用程序开发人员的作用。

  改变一个组件的展示不会有雪崩效应。现在您可以容易地只对renderer作出改变。在传统的MVC模型中,由于没有这种分离,对于标记的任何改变都需要改变业务逻辑。现在再不需要这样了。

  renderer无关性。也可以说是协议无关性,通过对带有多个renderer的多种展示设备重复使用组件逻辑实现。使用不同renderer的能力使得不再需要对特定的设备编写整个表示层代码。

  组装和重用自定义组件的标准。JSF的考虑范围超出了“表单和字段”,它提供了丰富的组件模型用以呈现自定义GUI组件。用JSF可以定制每一个组件在页面中的外观和行为。开发人员还拥有创建他们自己的GUI组件(如菜单和树)的能力,这些组件可以用简单的自定义标记容易地加入到任何JSP页面中。就像AWT和Swing所提供的Java前端GUI组件一样,我们可以在我们的Web页而中有自定义的组件,它们使用自己的事件处理程序并有定制的外观。这是Web层的GUI天堂!

  Struts是一种已经拥有大量客户基础的框架。许多IT部门认识到这种MVC框架的价值并使用它有一段时间了。JSF没有像Structs这样强大的控制器结构,也没有像它那样标准化的ActionForm和Actions(及它们声明的能力)。将Tiles集成到集合体中,就给了自己重复使用和以无缝的方式改变公司布局的能力。

  移植支持JSF的Struts应用程序的挑战是双重的。首先,Struts标记不是JSF兼容的。换句话说,它们没有像JSF规范所规定的那样扩展UIComponentTag,所以,JSF不能解释它们并关联到UIComponent和Renderers。

  其次,在FacesServlet与StrutsRequestProcessor之间没有链接。在Struts应用程序中,RequestProcessor负责用ActionForm和Actions类中的回调方法显示。ActionForm属性和validate()的getter和setter是ActionForm中的回调方法。对于Action,execute()是回调方法。除非调用了RequestProcessor,否则StrutsActionForm和Actions类中的回调方法没有机会调用业务逻辑。

  将Struts和JSF与Struts-Faces集成

  这里,您可能会问是否有软件可以帮助将Struts与JSF集成,或者是否必须自己编写集成软件。

  好消息是已经有这样的软件了。Struts-Faces是一个早期发布的StrutsJSF集成库。这个库是由CraigMcClanahan创建的,它使得将现有Struts应用程序移植到JSF变得容易了(保留了对现有Struts投资的价值)。Struts-Faces还力图与JSF进行简洁的集成,这样就可以在前端使用JSF,同时后端仍然有熟悉的Struts组件。

  图4展示了Struts-Faces与JSF类之间的关系。蓝色的类属于Struts-Faces。

  图4.Struts-Faces类图

  单击这里以查看该图。

  下面是Struts-Faces的主要组件:

  FacesRequestProcessor类,它处理所有faces请求。这个类继承了常规StrutsRequestProcessor,并处理faces请求。Non-faces请求发送给出其父类--RequestProcessor。

  ActionListenerImpl类,它处理像提交表单或者单击链接这样的ActionEvent。这个类用于代替由JSF-RI提供的默认ActionListener实现。只要在一个faces请求中生成ActionEvent,就会对ActionListenerImpl调用processAction()方法、并将ActionEvents转送给FacesRequestProcessor。这很有意思,因为RequestProcessor通常只由ActionServlet调用以处理HTTP请求。

  FormComponent类,它扩展了JSFForm组件,但是是在Struts生命周期内调用的。

  FormComponent的renderer和标记。

  只用于输出的数据标记和renderer,这里不需要分离组件。例如,ErrorsTag和ErrorsRenderer用于在HTML中显示表单错误。

  ServletContextListener的名为LifeCycleListener的实现。它用于在初始化时注册相应的RequestProcessor。

  faces-config.xml文件。这个文件已经捆绑在Struts-faces.jar文件中。

  清单3展示了使用Struts-Faces标记的FlightSearch.jsp。它类似于在清单1中展示的JSF例子。这里用粗体突出了区别之处。在这里,您会发现增加了一个新标记库tags-faces。这个标记库定义声明这些标记由Struts-FacesAPI所使用。

  清单3.FlightSearch.jsp使用Struts-Faces标记

  <%@tagliburi="http://java.sun.com/jsf/html"prefix="h"%>

  <%@tagliburi="http://java.sun.com/jsf/core"prefix="f"%>

  <%@tagliburi="http://jakarta.apache.org/Struts/tags-faces"

  prefix="s"%>

 

 

 

 

 

  valueRef="FlightSearchForm.departureDate">

 

  valueRef="FlightSearchForm.arrivalDate">

 

  commandName="submit"/>

 

  commandName="reset"/>

 

 

 

  s:form标记用于创建这个HTML表单。表单的action属性是/listFlights而不是像清单1那样指定为表单名flightForm。在JSF中,表单名只是指定给UIForm的名字而没有更多的意义。

  FlightSearchBean是JSF表单的模型,并在更新模型值阶段得到其值。不过在Struts中,表单action指向Struts配置文件Struts-config.xml中的ActionMapping。为了理解它是如何起作用的,还必须看一下清单4中显示的Struts-config.xml文件。

  您会看到/listFlights的ActionMapping表明这个URI路径的ActionForm是foo.bar.FlightSearchForm,而Action类是foo.bar.FlightSearchAction。换句话说,ActionForm(FlightSearchForm)本身就是Struts-Faces中的HTML表单的模型,它的action间接地指向这个模型(您可以在清单3中看到这一点,那里文本字段标记指向FlightSearchForm。在普通Struts应用程序中这会是 )。

  清单4.在Struts-config.xml中声明Action

 

  type="foo.bar.FlightSearchForm"/>

 

 

 

 

  type="foo.bar.FlightSearchAction"

  name="FlightSearchForm"

  scope="request"

  input="/faces/FlightSearch.jsp">

 

 

 

  您会注意到在action属性中缺少熟悉的.do。这是因为Struts-Faces使用表单action本身作为表单名(它还应该与Struts配置文件中的ActionForm名相匹配)。

  集成StrutsTiles的五个步骤

  以下五步可以让Struts1.1和Tiles共同工作:

  1.创建一个JSP以表示站点的布局。这是主JSP,并带有页头、页体和页脚的占位符。分别用Tiles标记添加到主JSP页面中。

  2.创建一个Tiles定义文件并定义每个集成页面的每个占位符中必须包括哪个JSP页面。用惟一的名称标识出每一个合成页面定义。

  3.在Struts-config.xml文件中改变全局和本地转发以使用上一步骤中给出的惟一名称而不是别名。

  4.在启动时用TilesPlugIn装载Tiles定义文件。将TilesPlugIn项加入到Struts-config.xml文件中。

  5.将TilesRequestProcessor项添加到Struts-config.xml文件中。这是支持TilesStruts应用程序的默认请求处理程序。

  还要注意我们在这里没有使用JSFvalidation标记。这是因为在Struts中,验证是在ActionForm类中的validate()方法中进行的,有可能是通过使用Commons-Validator。s:errors标记类似于Struts错误标记并用于显示在验证时出现的错误消息。

  另一件要注意的事情是没有ActionListener显式地与提交按钮相关联。这是因为在Struts-Faces中已经提供了ActionListener并且总是将faces请求与ActionEvents一同转交给FacesRequestProcessor,在那里根据Struts-config.xml文件将请求分派给相应的Action类。

  将Struts应用程序移植到JSF

  为了将StrutsWeb应用程序与JSF集成,遵循以下步骤:

  将Struts-faces.jar文件与特定于JSF的JAR(jsf-api.jar、jsf-ri.jar)添加到Web应用程序的WEB-INF/lib目录中。

  如果准备使用JSF和JSTL,则将特定于JSTL的JAR(jstl.jar、standard.jar)添加到WEB-INF/lib文件夹中。这一步只有在部署到常规Tomcat时才会需要。JWSDP已经提供了这些JAR。

  修改Web应用程序部署描述符(/WEB-INF/web.xml)以便有一个FacesServlet项,如清单5所示。

  修改JSP页面以使用JSF和Struts-Faces标记而不是Struts标记。特别是用Struts-Faces相应标记替换html、base、form和errors标记。用JSF相应标记替换text、textarea和radio标记。Struts-Faces没有单独针对这些的标记。尽管没有要求,但是您可能还会考虑用JSTL标记替换StrutsLogic标记。

  对于每一个使用JSF标记的JSP,修改Struts-config.xml文件以在指向该JSP的ActionMapping中的global-forwards和local-forwards中加入前缀/faces。

  如果Web应用程序使用了任何您创建的自定义组件,那么您就需要用JSF实现的默认RenderKit注册它们。可以通过在WEB-INF文件中创建一个faces-config.xml文件、并增加每一个组件和renderer的项做到这一点。不过,要记住faces-config.xml文件已经绑定在Struts-faces.jar文件中了。您必须从Struts-faces.jar文件中提出它、加入自己的内容并将它放到WEB-INF文件夹中。

  清单5.在web.xml中声明FacesServlet

 

 

  faces

  javax.faces.webapp.FacesServlet

  1

 

 

 

  faces

  /faces/*

 

  集成Struts-Faces和Tiles的挑战

  Struts-Faces库提供了Struts与JSF之间的一个高效的桥梁,使得在J2EEWeb应用程序中拥有丰富的表示层成为现实。您可以通过在组合体中添加Titles使表示层更丰富,这样不仅得到了Struts和JSF组合的好处,而且还可以高效地重复使用不同的JSP页面,因为它们将由可以根据需要添加或者删除的组件部分或者Tiles所构成。

  本文已经展示了Struts和JSP的集成,您会想将Tiles加入到组合中只是小事一桩,是不是?

  不幸的是,JSF仍然处于早期阶段,还没有给出最后的发布。基于这一考虑,Struts-Faces集成软件开发仍然在不断地发展以包括JSF的不同的功能,并且还没有支持Tiles

  StrutsTiles可以无缝地共同工作,但是在集成之路上您会遇到路障。在下面几小节中,您会看到在与Tiles共同使用Struts-Faces集成库时经常遇到的问题的汇总。对于每一个问题,我们详细说明了一个修改Struts-Faces类的解决方案。我们将用一个航班搜索示例解释这个解决方案。

  清单6展示了航班搜索页面的布局。注意我们称它为航班搜索页面而不是FlightSearch.jsp。这是因为FlightSearchJSP是用户在foobar旅行Web站点看到的合成页面的主体。

  现在,我们保持实际的FlightSearch.jsp不变。我们将随着进展改变它。在您这边,也需要用航班搜索页的定义创建一个Tiles定义文件。清单7(紧接着清单6)展示了Tiles定义文件中航班搜索页的一项。注意对带有extends属性的主布局模板的重复使用。

  在清单6和7后是每一个可能的挑战。

  清单6.航班搜索例子的Tiles布局

  <%@tagliburi="/WEB-INF/Struts-Tiles.tld"prefix="Tiles"%>

  <%@tagliburi="http://jakarta.apache.org/Struts/tags-faces"prefix="s"%>

 

 

 

  <<A href="http://www.66of.com" target=_blank>Tiles</A>:getAsStringname="title"/>

 

 

 

 

 

  <Tiles:insertattribute="header"/>

 

 

  <Tiles:insertattribute="body"/>

 

 


 

  <Tiles:insertattribute="footer"/>

 

 

 

 

  清单7.航班搜索页的Tiles定义

 

 

  path="/faces/layout/MasterLayout.jsp">

 

 

 

 

 

 

 

  extends="foobar.master-layout">

 

 

  响应已经提交

  这是您在试图访问航班搜索表单时马上会看到的第一个问题。小心查看堆栈跟踪。您会看到问题出在类com.sun.faces.lifecycle.ViewHandlerImpl上。这是一个实现了ViewHandler接口的JSF-RI类。

  图2展示了ViewHandler所扮演的角色。这是一个将请求转发给下一页的类。在转发请求时,它不在转发前检查响应的状态--这只有在使用Tiles时才会发生,因为Tiles内部将JSP页面包括在响应内,而JSF-RI在第一次转发后提交响应、然后试图再次转发给下面的包括JSP的Tiles

  要解决这个问题,必须创建一个自定义的ViewHandler实现,它将检查响应的状态以确定它是否提交过。如果响应没有提交过,那么请求就转发给下一页,否则,就加入请求并显示相应的JSP。我们将创建一个名为STFViewHandlerImpl的类,它实现了ViewHandler接口并实现了所需要的方法renderView()。清单8展示了STFViewHandlerImpl中的renderView()方法:

  清单8.STFViewHandlerImpl中的renderView()方法

  RequestDispatcherrd=null;

  Treetree=context.getTree();

  StringrequestURI=context.getTree().getTreeId();

  rd=request.getRequestDispatcher(requestURI);

  /**Iftheresponseiscommitted,includetheresource**/

  if(!response.isCommitted()){

  rd.forward(request,context.getServletResponse());

  }

  else{

  rd.include(request,context.getServletResponse());

  }

  现在您实现了自己的ViewHandler,如何通知JSF-RI使用您的ViewHandler而不是默认的实现呢?要回答这个问题,就必须理解FacesServlet的工作过程。

  在Faces初始化过程中,FacesServlet会让LifecycleFactory实现返回Lifecycle类的一个实现,如清单9所示:

  清单9.FacesServlet中Faces的初始化

  //GettheLifecycleFactoryfromtheFactoryFinder

  LifecycleFactoryfactory=(LifecycleFactory)

  FactoryFinder.getFactory("javax.faces.lifecycle.LifecycleFactory");

  //Getthecontextparamfromweb.xml

  StringlifecycleID=

  getServletContext().getInitParameter("javax.faces.lifecycle.LIFECYCLE_ID");

  //GettheLifecycleImplementation

  Lifecyclelifecycle=factory.getLifecycle(lifeCycleID);

  Lifecycle实现对象拥有在呈现响应阶段要使用的ViewHandler。您可以通过对Lifecycle实现调用setViewHandler方法让自己的ViewHandler实现成为默认的。

  现在问题变为如何得到默认Lifecycle实现?回答是不需要这样做。只要创建一个新的实现并用一个惟一ID注册它,如清单10所示:

  清单10.注册自定义ViewHandler和Lifecycle

  //GettheLifecycleFactoryfromtheFactoryFinder

  LifecycleFactoryfactory=(LifecycleFactory)

  FactoryFinder.getFactory("javax.faces.lifecycle.LifecycleFactory");

  //CreateanewinstanceofLifecycleimplementation-

  //com.sun.faces.lifecycle.LifecycleImpl

  //Accordingtothedocumentation,factory.getLifecycle("STFLifecycle")

  //shouldwork,butJSF-RIhasadefect.

  //HencethisworkaroundofcreatingaRIclassexplicitly.

  LifecycleImplstfLifecycleImpl=newLifecycleImpl();

  //CreateanewinstanceofourSTFViewHandlerandsetitontheLifecycle

  stfLifecycleImpl.setViewHandler(newSTFViewHandlerImpl());

  //Registerthenewlifecyclewiththefactorywithaunique

  //name"STFLifecycle"

  factory.addLifecycle("STFLifecycle",stfLifecycleImpl);

  您可以看到lifecycleId硬编码为STFLifecycle。实际上不是这样。当您回过头分析清单9时就会清楚。FacesServlet从在web.xml文件中声明的上下文参数中得到名为javax.faces.lifecycle.LIFECYCLE_ID的lifecycleID,如下所示:

 

  javax.faces.lifecycle.LIFECYCLE_ID

  STFLifecycle

 

  因为FacesServlet取决于其初始化时的Lifecycle实现,在清单10中展示的代码应该在FacesServlet初始化之前执行。通过创建另一个servlet并在FacesServlet之前初始化它而做到这一点。

  但是一种更聪明的办法是实现一个ServletContextListener接口。这个类声明两个方法:contextInitialized()和contextDestroyed(),在Web应用程序被创建及Web应用程序被销毁之前会分别调用它们。因而清单10中的代码在contextInitialized()方法中执行,而自定义ViewHandler已经用标识名STFLifecycle注册到Lifecycle,并且可被FacesServlet使用。ServletContextListener类本身是在web.xml文件中声明的,如下所示:

 

  foo.bar.stf.application.STFContextListener

 

 

  这不是注册一个带有自定义ViewHandler的Lifecycle惟一方法。事实上FactoryFinder实现了自己的发现算法以发现Factory对象,包括LifecycleFactory。这些机制按照顺序包括在系统属性中查看工厂实现类名的机制、faces.propertiesfile、或者1.3Services发现机制(META-INF/services/{factory-class-name})。不过,我们讨论的这种机制是最容易的,也是最不具有破坏性的一种。

  404ResourceNotFound

  在解决了提交响应的问题后,单击任何一个Tiles特定的链接或者输入一个会呈现Faces响应的URL。在这里,可以输入显示FlightSearchForm的URL。

  在这样做了以后,您会得到一个foobar.flight-search-404ResourceNotFound错误。foobar.flight-search是航班搜索页面的Tiles定义的名字。FacesRequestProcessor不能处理Tiles请求(因为它扩展的是RequestProcessor而不是TilesRequestProcessor),所以会得到错误。

  为解决这个问题,我们将创建一个名为STFRequestProcessor(表示Struts-Tiles-FacesRequestProcessor)的新的请求处理程序。现在我们将拷贝FacesRequestProcessor的所有代码到这个新类中。惟一的区别是STFRequestProcessor继承的是TilesRequestProcessor而不是继承常规的RequestProcessor。这个新的RequestProcessor可以处理Tiles请求。清单11详细列出了这个STFRequestProcessor:

  清单11.STFRequestProcessor.java

  正如您所知道的,Struts框架的RequestProcessor是在Struts-config.xml文件中指定的。将下面的项添加到Struts-cinfig.xml文件中后,STFRequestProcessor就成为处理程序:

 

  表单提交显示返回同一个表单

  由于STFRequestProcessor的作用,这时您就可以浏览并查看航班页面了。不过,在提交航班搜索表单时,您会得到返回来的同一个表单,而且没有页头和页脚!并且没有验证错误。事实上,根本就没有进行验证!

  为了了解到底发生了什么事情,我们用浏览器回到航班页面并检查HTML源代码。您会看到像下面这样的一项:

 

  action="/flightapp/faces/FlightSearch.jsp">

  注意表单action是指向JSP页而不是一个.do的。啊哈!这就是问题!这不是由于同时使用TilesStruts-Faces而带来的新问题,Struts-Faces的默认行为是让JSP与表单action有同样的名字。这种行为在有单一的JSP页(如在前面的Struts-Faces例子中)时没有问题。清单3展示了原来的FlightSearch.jsp,让我们继续并像下面这样修改action:

 

  当然,光有这种修改并不能解决问题。作了这种改变后,您就会发现STFRequestProcessor不能找到ActionForm。显然还需要其他的改变。

  不过,在继续往下之前,看一下图5。它显示了在呈现负责Struts-Faces表单的faces时相关的一系列事件。这与图3相同,除了在FormComponent中突出显示的方法createActionForm()。由Struts-FacesAPI提供的FormComponent类是javax.faces.component.UIForm的特殊子类,它支持请求或者会话范围的表单Bean。

  图5.呈现Struts-Faces响应

  单击这里以查看该图。

  正如您所看到的,createActionForm()方法使用action名以从Struts配置文件中得到ActionMapping。因为没有对于/listFlights.do的ActionMapping,所以Struts不能找到ActionForm。

  这个问题的解决方法是使用org.apache.Struts.util.RequestUtils。RequestUtils中的static方法getActionMappingName()具有足够的智能解析映射到正确ActionMapping的路径(/x/y/z)或者后缀(.do)。

  清单12以粗体显示对createActionForm方法的改变。我们没有对Struts-Faces中的FormComponent作这些改变,而是通过继承FormComponent并覆盖createActionForm()方法创建了一个新的STFFormComponent。

  清单12.FormComponent中修改过的createActionForm()方法

  //Lookuptheapplicationmoduleconfigurationinformationweneed

  ModuleConfigmoduleConfig=lookupModuleConfig(context);

  //LookuptheActionConfigweareprocessing

  Stringaction=getAction();

  StringmappingName=RequestUtils.getActionMappingName(action);

  ActionConfigactionConfig=moduleConfig.findActionConfig(mappingName);

  ....

  ....

  对新的STFFormComponent还要作一项改变。Struts-Faces将action名本身作为表单名。这需要改变,因为action带有后缀.do,而表单名没有后缀.do。所以我们在STFFormComponent上增加一个名为action的新属性,并覆盖getAction()和setAction()方法。

  FormRenderer的改变

  必须对FormRenderer(以HTML格式呈现Struts-Faces表单的类)的encodeBegin方法进行类似于清单10所示的修改。

  同样,通过继承FormRenderer做到这一点。此外,还必须改变写出到HTML的表单action。清单13以粗体详细列出了这些改变:

  清单13.FormRenderer的改变

  protectedStringaction(FacesContextcontext,UIComponentcomponent){

  StringtreeId=context.getTree().getTreeId();

  StringBuffersb=newStringBuffer

  (context.getExternalContext().getRequestContextPath());

  sb.append("/faces");

  //sb.append(treeId);--Thisisoldcode,replacedwith

  //thetwolinesbelow.

  STFFormComponentfComponent=(STFFormComponent)component;

  sb.append(fComponent.getAction());

  return(context.getExternalContext().encodeURL(sb.toString()));

  }

  FormTag的改变

  正如您已经知道的,当组件和renderer改变时,标记也必须改变。在这里,通过继承Struts-Faces中的FormTag创建一个新的标记:STFFormTag。不必改变任何功能,只要覆盖getComponentType()和getRendererType()方法。清单14展示了从STFFormComponent覆盖的方法:

  清单14.FormTag的改变

  publicStringgetComponentType()

  {

  return("STFFormComponent");

  }

  publicStringgetRendererType()

  {

  return("STFFormRenderer");

  }

  修改faces-config.xml文件

  自定义组件和renderer必须在faces-config.xml文件中声明,这样JSF框架才可以初始化并使用它们。现在我们已经创建了一个新组件STFFormComponent和一个新rendererSTFFormRenderer。

  现在我们将在faces-config.xml文件中增加一个声明,如清单15所示。component-class是组件的完全限定类名。component-type指的是在STFFormTag(清单12)中用于标识组件的名字。以类似的方式发现和解释renderer。注意faces-config.xml文件是在Struts-faces.jar文件中的。从Struts-faces.jar文件中取出这个文件并将它放到Web应用程序的WEB-INF文件夹中并修改它。

  清单15.在faces-config.xml中声明自定义组件和renderer

 

 

 

  STFFormComponent

 

  foobar.stf.component.STFFormComponent

 

 

  ..

  ..

  ..

 

 

 

  STFFormRenderer

 

  foobar.stf.renderer.STFFormRenderer

 

 

  ..

  ..

  ..

 

 

  修改Struts-faces.tld文件

  您不会在这个示例Struts-Faces应用程序中看到Struts-faces.tld文件,它打包到了Struts-faces.jar文件中。打开并分析这个文件。它声明了一个名为org.apache.Struts.faces.taglib.LifecycleListener的类,这个类实现了ServletContextListener并初始化FacesRequestProcessor。

  因为希望使用新的STFRequestProccessor,所以必须将这个文件从Struts-faces.jar文件中删除,将它放到Web应用程序的WEB-INF文件夹中,并删除侦听器声明。如果让这个tld文件保持原样,那么在初始化这个Web应用程序时,除了STFRequestProcessor,还会实例化一个FacesRequestProcessor。

  修改basehref标记

  现在,您已经完成了StrutsTiles、JSF集成的最困难的部分了。您甚至可以浏览航班搜索页面,并输入搜索标准查看航班列表。现在试着从航班列表页面返回航班搜索表单。您会得到一个HTTP400错误。这个错误的原因是HTMLbasehref标记。它被设置为MasterLayout页面。

 

  "http://localhost:8080/stf-example/faces/layout/MasterLayout.jsp"/>

  |_________||_____________________|

  ContextServletPath

  程序所有页面浏览都是相对于布局页面计算的。如果加入的basehref标记只达到Web应用程序上下文则会很方便,像这样:

  Struts-FacesBaseTag做到这一点。这个类中的改变相当微不足道。只须在basehref中去掉HttpServletRequest.getServletPath()。

  因为这些改变是与显示相关的,所以为它创建了一个名为STFBaseRenderer的新renderer。这个新标记称为STFBaseTag,它声明STFBaseRenderer作为其关联的renderer。不需要新的组件。

  有了这些信息,通过继承BaseTag并覆盖getRendererType方法创建新的STFBaseTag,如下所示:

  publicStringgetRendererType()

  {

  return("STFBaseRenderer");

  }

  到目前为止所作的改变

  恭喜!经过这些相对较小的修改,您已经成功地集成StrutsTiles和JSF,并保留了您以前在这些技术上所做的所有投资。本文演示了如何将JSF强大的前端能力、Tiles的内容格式编排优势以及Struts控制器层的灵活性结合在一个包中,使得创建一个J2EEWeb应用程序成为一项更容易的任务。

  我们讨论了定制Struts类以便与JavaServerFaces和Tiles框架形成紧密集成的工作关系,包括下面这些修改和增加:

  新的ViewHandler,用于检查提交的响应。

  新的ServletContextListener,用于创建新的Lifecycle实现并注册这个定制的ViewHandler。

  新的RequestProcessor,用于处理Tiles请求。

  修改过的web.xml文件,声明新的ServletContextListener和JSFLifecycleID。

  新的FormTag、FormComponent和FormRenderer类。

  新的BaseTag和BaseRenderer类。

  修改过的faces-config.xml文件,它声明了新的组件和renderer。

  修改过的Struts-faces.tld文件,不声明侦听器。

  希望它可以概括本文中使用的复合技术,最重要的是,我们为您提供了将StrutsTiles和JavaServerFaces结合到用于构建Web应用程序的一个强大而灵活的机制中的一个令人信服的路线图。

  参考资料

  下载本文的例子和代码,并遵循README.txt中给出的编译和部署的说明。

  Ant用于对例子进行编译,可以从ApacheAnt项目Web站点下载它。

  有关StrutsTiles的更多内容,包括可下载的教程、文档、二进制文件和源代码,可从ApacheJakartaProjectStrutsWeb站点获得。

  可以将JSFEarlyAcessRelease4(EA4)作为JavaWebServicesDeveloperPackVersion1.2的一部分下载--它带有自己版本的Tomcat。

  可以从Jakarta站点下载Struts-Facesintegrationlibrary的0.3和0.4版本。

  可以从JavaWebServicesDeveloperPack1.2下载JSF-RI。

  “Struts,anopen-sourceMVCimplementation”(developerWorks,2001年2月)介绍了Struts,这是一个使用了servlets和JavaServerPages技术的模型-视图-控制器实现。

  “StrutsandTilesaidcomponent-baseddevelopment”(developerWorks,2002年6月)解释了为什么结合StrutsTiles可以成为创建Web应用程序的出色软件包,并展示了如何使用它,侧重于Struts0.9之后的改变。

  “Struttin'yourstuffwithWebSphereStudioApplicationDeveloper,Part2:Tiles”(developerWorks,2002年11月)是一个教程,主要关注在使用WebShpereStudioApplicationDeveloper作为开发环境时结合Struts使用Tiles模型框架。

  “ArchitectStrutsapplicationsforWebservices”(developerWorks,2003年4月)展示了如何基于MVB设计模式用Struts建立Web服务应用程序。

  “AJSTLprimer”(developerWorks,2003年2-5月),这是一个分为四部分的系列,提供了有关JSTL的所有内容,包括如何使用JSTL标记以避免在JSP页面中使用脚本元素、如何通过删除表示层中的代码简化软件维护、以及JSTL的简化的表达式语言,它使得无需使用全功能的编程语言就可以为JSTLaction指定动态属性值。

  学习用JSF开发Web应用程序的基本内容。在其教程“UIdevelopmentwithJavaServerFaces”(developerWorks,2003年9月)中,JackwindLiGuojie探讨了JSF生命周期、输入验证、事件处理、页面浏览和国际化。

  Sun的JSFWeb站点是另一个很好的学习有关JavaServerFaces技术的起点。

  ServerSide.comJ2EE社区是查找有关J2EE的资源及参加开发者论坛的理想地点。

  在JavaCommunityProcess站点可以迅速得到有关JavaServerPages1.2规范的内容。

  在developerWorksJava技术专区可以找到关于Java编程各方面的数百篇文章。

  关于作者

  SrikanthShenoy专门从事大型J2EE和EAI项目的体系结构、设计、开发和部署工作。Srikanth已经帮他的制造业、物流业和金融业客户实现了Java平台“一次编写,随处运行”的梦想。他是Sun认证的企业架构师,并且是即将出版的PracticalGuidetoJ2EEWebProjects一书的作者之一。可以通过srikanth@srikanth.org与他联系。

  NithinMallya专门为金融客户提供企业级解决方案。他有七年架构和开发服务器端解决方案的经验,大多数是Java平台。他是Sun认证的企业架构师,并且是Sun认证的Web组件开发者。他也是即将出版的PracticalGuidetoJ2EEWebProjects一书的作者之一。可以通过nithin@mallya.org与他联系。

<script type="text/javascript"> </script> <script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>
<script type="text/javascript"> </script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值