浏览模式: 标准 | 列表全部文章

Struts用户指南

1. 介绍

  1.1 Model-View-Controller (MVC) 设计模式

  FIXME - 需要一个对该模式一般性的介绍。(译注:可以参考机械工业出版社的《设计模式》。)

  1.2 将MVC概念映射到Struts组件中

  Struts 的体系结构实现了Model-View-Controller设计模式的概念,它将这些概念映射到web应用程序的组件和概念中.

  这一体系结构中每个主要的组件都将在下面做详细的讨论。

  1.3 Model: 系统状态和商业逻辑JavaBeans

  基于MVC的系统中的 Model 部分可以细分为两个概念 -- 系统的内部状态, 能够改变状态的行为。用语法术语来说,我们可以把状态信息当作名词(事物),把行为当作动词(事物状态的改变)。

  通常说来,你的应用程序将系统内部的状态表示为一组一个或多个的JavaBeans,使用属性(properties)来表示状态的细节。依赖于你的应用程序的复杂度,这些beans可以是自包含的(以某种方式知道怎样永久地保存它们的状态信息),或者可以是正面的(facades),知道当被请求时怎样从外部数据源(例如数据库)中取得信息。Entity EJBs通常也用来表示内部状态。

  大型应用程序经常将系统可能的商业逻辑行为表示为可以被维护状态信息的beans调用的方法。举个例子,你有一个为每个当前用户保存在session中的购物车bean,里面是表示当前用户决定购买物品的属性。这个bean有一个checkOut()方法用来验证用户的信用卡,将定单发给库房以选择货品和出货。别的系统分别地表示同样的行为,或许使用Session EJBs。

  在一些小型应用程序中,同样的行为又可能嵌入到作为Controller一部分的 Action 类中。这在逻辑非常简单或者并不想要在其它环境中重用这些商业逻辑时是恰当的。Struts框架支持所有这些方法,但建议将商业逻辑(“做什么”)和 Action 类(“决定做什么”)分离开。

  1.4 View: JSP页面和表示组件

  基于Struts的应用程序中的 View 部分通常使用JSP技术来构建。JSP页面包含称为“模版文本”的静态HTML(或XML)文本,加上插入的基于对特殊行为标记解释的动态内容。JSP环境包括了其用途由JSP规范来描述的一套标准的行为标记,例如 <jsp:useBean> 。另外,还有一个用来定义你自己标记的标准机制,这些自定义的标记组织在“定制标记库”中。

  Struts包括了一个广阔的便于创建用户界面,并且充分国际化的定制标记库,与作为系统 Model 部分一部分的ActionForm beans美妙地相互配合。这些标记的使用将在后面做详细讨论。

  除了JSP页面和其包含的行为及定制标记,商业对象经常需要能够基于它们在被请求时的当前状态将自己处理成HTML(或XML)。从这些对象处理过的输出可以很容易地使用 <jsp:include> 标准行为标记包括在结果的JSP页面中。

  1.5 Controller: ActionServlet和ActionMapping

  应用程序的 Controller 部分集中于从客户端接收请求(典型情况下是一个运行浏览器的用户),决定执行什么商业逻辑功能,然后将产生下一步用户界面的责任委派给一个适当的View组件。在Struts中,controller的基本组件是一个 ActionServlet 类的servlet。这个servlet通过定义一组映射(由Java接口 ActionMapping 描述)来配置。每个映射定义一个与所请求的URI相匹配的路径和一个 Action 类(一个实现 Action 接口的类)完整的类名,这个类负责执行预期的商业逻辑,然后将控制分派给适当的View组件来创建响应。

  Struts也支持使用包含有运行框架所必需的标准属性之外的附加属性的 ActionMapping 类的能力。这允许你保存特定于你的应用程序的附加信息,同时仍可利用框架其余的特性。另外,Struts允许你定义控制将重定向到的逻辑名,这样一个行为方法可以请求“主菜单”页面(举例),而不需要知道相应的JSP页面的实际名字是什么。这个功能极大地帮助你分离控制逻辑(下一步做什么)和显示逻辑(相应的页面的名称是什么)。

  2. 创建Model组件

  2.1 概述

  你用到的应用程序的需求文档很可能集中于创建用户界面。然而你应该保证每个提交的请求所需要的处理也要被清楚的定义。通常说来,Model 组件的开发者集中于创建支持所有功能需求的JavaBeans类。一个特殊应用要求的beans的精确特性依赖于具体需求变化会非常的大,但是它们通常可以分成下面讨论的几种类型。然而,首先对“范围”概念做一个简短的回顾是有用的,因为它与beans有关。

  2.2 JavaBeans和范围

  在一个基于web的应用程序中,JavaBeans可以被保存在(并从中访问)一些不同“属性”的集合中。每一个集合都有集合生存期和所保存的beans可见度的不同的规则。总的说来,定义生存期和可见度的这些规则被叫做这些beans的 范围 。JSP规范中使用以下术语定义可选的范围(在圆括号中定义servlet API中的等价物):

  page - 在一个单独的JSP页面中可见的Beans,生存期限于当前请求。(service()方法中的局部变量)

  request - 在一个单独的JSP页面中可见的Beans,也包括所有包含于这个页面或从这个页面重定向到的页面或servlet。(Request属性)

  session - 参与一个特定的用户session的所有的JSP和servlet都可见的Beans,跨越一个或多个请求。(Session属性)

  application - 一个web应用程序的所有JSP页面和servlet都可见的Beans。(Servlet context属性)

  记住同一个web应用程序的JSP页面和servlets共享同样一组bean集合是很重要的。例如,一个bean作为一个request属性保存在一个servlet中,就象这样:

MyCart mycart = new MyCart(...);

request.setAttribute("cart", mycart);

  将立即被这个servlet重定向到的一个JSP页面使用一个标准的行为标记看到,就象这样:

<jsp:useBean id="cart" scope="request"

class="com.mycompany.MyApp.MyCart"/>

  2.3 ActionForm Beans

  Struts框架通常假定你已经为每一个你的应用程序中请求的输入创建了一个 ActionForm bean(即一个实现了 ActionForm 接口的类)。如果你在你的 ActionMapping 配置文件中定义了这样的beans(见“创建Controller组件”),Struts的controller servlet在调用适当的 Action 方法前将自动为你执行如下的服务:

  用适当的关键字检查用户的session中是否有适当的类的bean的一个实例。

  如果没有这样的session范围的bean,自动建立一个新的bean并添加到用户的session中。

  对每个名字对应于bean中的一个属性的请求参数,调用相应的set方法。这个操作类似于当你以通配符“*”选择所有属性使用标准的JSP行为标记 <jsp:setProperty> 。

  更新的ActionForm bean在被调用时将被传递给Acton类的perform()方法,以使这些值能够立即生效。
当你在写你的ActionForm beans时,记住以下的原则:

  ActionForm 接口本身不需要特殊的实现方法。它是用来标识这些特定的beans在整个体系结构中的作用。典型情况下,一个ActionForm bean只包括属性的get方法和set方法,没有商业逻辑。

  通常在一个ActionForm bean中只有很少的输入验证逻辑。这样的beans存在的主要理由是保存用户为相关的表单所输入的大部分近期值 -- 甚至在错误被检测到时 -- 这样同样的页面可以被重建,伴随有一组出错信息,这样用户仅仅需要纠正错误的字段。用户输入的验证应该在 Action 类中执行(如果是很简单的话),或者在适当的商业逻辑beans中执行。

  为每个表单中出现的字段定义一个属性(用相关的getXxx()和setXxx()方法)。字段名和属性名必须按照JavaBeans的约定相匹配。例如,一个名为 username 的输入字段将引起 setUsername() 方法被调用。

  你应该注意一个“表单”在这里讨论时的意义并不必须对应于用户界面中的一个单独的JSP页面。在很多应用程序中一个“表单”(从用户的观点)延伸至多个页面也是很平常的。想想看,例如,通常在安装新的应用程序时使用的导航安装程序的用户界面。Struts鼓励你定义一个包含所有字段属性的单独的ActionForm bean。不管字段实际上是显示在哪个页面上。同样的,同一表单的不同的页面应该提交到相同的Action类。如果你遵照这个建议,在大多数情况下,页面设计者可以重新组织不同页面中的字段而不需要改变处理逻辑。

  2.4 系统状态Beans

  系统的实际状态通常表示为一组一个或多个的JavaBeans类,其属性定义当前状态。例如,一个购物车系统包括一个表示购物车的bean,这个bean为每个单独的购物者维护,这个bean中包括(在其它事物之中)一组购物者当前选择购买的项目。分别地,系统也包括保存用户信息(包括他们的信用卡和送货地址)、可获得项目的目录和它们当前库存水平的不同的beans。

  对于小规模的系统,或者对于不需要长时间保存的状态信息,一组系统状态beans可以包含所有系统曾经经历的特定细节的信息。或者经常是,系统状态beans表示永久保存在一些外部数据库中的信息(例如CustomerBean对象对应于表 CUSTOMERS 中的特定的一行),在需要时从服务器的内存中创建或清除。在大规模应用程序中,Entity EJBs也用于这种用途。

2.5 商业逻辑Beans

  你应该把你的应用程序中的功能逻辑封装成对为此目的设计的JavaBeans的方法调用。这些方法可以是用于系统状态beans的相同的类的一部分,或者可以是在专门执行商业逻辑的独立的类中。在后一种情况下,你通常需要将系统状态beans传递给这些方法作为参数处理。

  为了代码最大的可重用性,商业逻辑beans应该被设计和实现为它们不知道自己被执行于web应用环境中。如果你发现在你的bean中你必须import一个 javax.servlet.* 类,你就把这个商业逻辑捆绑在了web应用环境中。考虑重新组织事物使你的 Action 类(Controller任务的一部分,在下面描述)翻译所有从HTTP请求中请求被处理为对你的商业逻辑beans属性set方法调用的信息,然后可以发出一个对 execute() 的调用。这样的一个商业逻辑类可以被重用在除它们最初被构造的web应用程序以外的环境中。

 

  依赖于你的应用程序的复杂度和范围,商业逻辑beans可以是与作为参数传递的系统状态beans交互作用的普通的JavaBeans,或者使用JDBC调用访问数据库的普通的JavaBeans。而对于较大的应用程序,这些beans经常是有状态或无状态的EJBs。

  2.6 题外话: 访问关系数据库

  很多web应用程序利用一个关系数据库(通过一个JDBC driver访问)来保存应用程序相关的永久数据。 其它应用程序则使用Entity EJBs来实现这个目的,他们委派EJBs自己来决定怎样维护永久状态。如果你是使用EJBs来实现这个目的,遵照EJB规范中描述的客户端设计模式。

  对于基于直接数据库访问的web应用程序,一个普通的设计问题是当需要访问低层数据库时怎样产生一个适当的JDBC连接对象。解决这个问题有几种方法 -- 以下原则描述了推荐的一种方法:

  创建或得到一个允许一组数据库连接被多个用户共享的ConnectionPool类。Struts(当前)没有包括这样的一个类,但是有很多这样的类可以得到。

  当应用程序初始化时,在应用程序展开(deployment)描述符中定义一个有一个“启动时加载”值的servlet。我们将把这个servlet叫做 启动 servlet。在大多数情况下,这个servlet不需要处理任何的请求,所以没有一个 <servlet-mapping> 会指向它。

  在启动servlet的 init() 方法中,配置并初始化一个ConnectionPool类的实例,将其保存为一个servlet context属性(从JSP的观点看等同于一个application范围的bean)。通常基于传递给启动servlet初始化参数来配置联接缓冲池是很方便的。

  在启动servlet的 destroy() 方法中,包含了释放联接缓冲池所打开的联接的逻辑。这个方法将在servlet容器结束这个应用程序的时候被调用。

  当 Action 类需要调用一个需要数据库联接的商业逻辑bean中的方法(例如“insert a new customer”)时,将执行下面的步骤:

  为这个web应用程序从servelt context属性中得到一个联接缓冲池对象。

  调用联接缓冲池对象的 open() 方法来得到一个在 Action 类调用中使用的联接。

  调用商业逻辑bean中合适的方法,将数据库联接对象作为一个参数传递给它。

  调用分配的联接中的 close() 方法,这将引起这个联接为了以后其它请求的重用被返回到缓冲池中。

  一个通常的编程错误是忘记了把数据库联接返回给缓冲池,这将最终导致用完所有的联接。一定要确信 Action 类的逻辑总是返回联接,甚至在商业逻辑bean抛出一个违例时。

  遵照上面推荐的设计模式意味着你能够编写你的商业逻辑类而不需要担心它们怎样得到一个JDBC联接来使用-- 简单地在任何需要访问数据库的方法中包含一个Connection参数。当你的商业逻辑类在一个web应用程序中被利用时,分配和释放适当的联接是 Action 类的责任。当你使用相同的商业逻辑类时,例如,在一个批处理工作中,提供一个适当的联接是那个应用程序的责任(这不需要从缓冲池中获得,因为大多数批处理工作运行于一个单线程环境中)。

  3. 创建View组件

  3.1 概述

  这一章集中于创建应用程序中的 View 组件的任务,主要使用JSP技术建立。特别的,Struts除了提供了与输入表单的交互外还提供了建立国际化应用程序的支持。几个其它的与View相关的主题也被简单地讨论。

  3.2 国际化消息

  几年之前,应用程序开发者能够考虑到仅仅支持他们本国的只使用一种语言(或者有时候是两种)和通常只有一种数量表现方式(例如日期、数字、货币值)的居民。然而,基于web技术的应用程序的爆炸性增长,以及将这些应用程序展开在Internet或其它被广泛访问的网络之上,已经在很多情况下使得国家的边界淡化到不可见。这种情况转变成为一种对于应用程序支持国际化(经常被称做“i18n”,因为18是字母“i”和字母“n”之间的字母个数)和本地化的需求。

  Struts建立于Java平台之上为建立国际化和本地化的应用程序提供帮助。需要熟悉的关键概念是:

  Locale - 基础的支持国际化的Java类是 java.util.Locale 。每个 Locale 代表一个特别的国家和语言选择(加上一个可选的语言变量),以及一套格式假定,例如数字和日期等等。

  ResourceBundle - java.util.ResourceBundle 类提供支持多种语言消息的基本工具。查看文档中关于 ResourceBundle 类以及你的JDK版本的文档包中关于国际化的更多内容。

  PropertyResourceBundle - 一个 ResourceBundle 类的标准实现允许你使用与初始化properties文件同样的“name=value”语法来定义资源。这对于使用为用于一个web应用程序的消息准备资源包是非常方便的,因为这些消息通常都是面向文本的。

  MessageFormat - java.text.MessageFormat 类允许你使用运行时指定的参数替换一个消息字符串中的一部分(在这种情况下,是一个从一个资源包得到的消息)。这在你创建一个句子的场合中是有用的,但是词会以不同的语言按照不同的顺序出现。消息中的占位符字符串{0}用第一个运行时参数替换,{1}用第二个运行时参数替换,以此类推。

  MessageResources - Struts的类 org.apache.struts.util.MessageResources 使你能够将一套资源包视做一个数据库,并且允许你为一个特定的Locale(通常是与当前用户相对应)请求一个特定的消息,而不是为服务器运行在其中的缺省的Locale请求消息。

  对于一个国际化的应用程序,遵照JDK文档包中国际化文档所描述的步骤来创建一个包含每种语言的消息的属性文件。下面举一个例子说明。

  假设你的源代码建立在包 com.mycompany.mypackage 中,因此它保存于一个叫做(相对于你的源目录)com/mycompany/mypackage 的目录中。为创建一个叫做 com.mycompany.mypackage.MyResources 的资源包,你应该在目录 com/mycompany/mypackage 中创建下列文件:


  MyResources.properties - 包含你的服务器的缺省语言的消息。如果你的缺省语言是英语,你可能有一个这样的条目:

prompt.hello=Hello

  MyResources_xx.properties - 包含ISO语言编程为“xx”(查看ResourceBundle的Java文档页面得到一个当前列表的联接)的同样的消息。对于上面的消息的法语版,你可以有这个条目:

prompt.hello=Bonjour

  你可以有你需要的任意多的语言的资源包文件。

  当你在web应用程序展开描述符中配置controller servlet时,你需要在一个初始化参数中定义的一件事是应用程序的资源包的基础名。在上述的情况中,这应该是 com.mycompany.mypackage.MyResources 。

3.3 表单和FormBean的交互

  大部分web开发者曾经使用HTML的标准性能来建立表单,例如使用 <input> 标记。用户希望交互程序具有一定的行为,这些期待中的一个与错误处理有关 -- 如果用户出现一个错误,应用程序应该允许他们仅仅修改需要修改的部分 -- 而不需要重新敲入当前页面或表单中的任何其它信息。

  使用标准的HTML和JSP编程来完全实现这个期望是单调而繁重的。举例来说,一个用户名字段的输入元素看起来可以象是这样(在JSP中)

<input type="text" name="username"

value="<%= loginBean.getUsername() %>">

  这很难敲对,会把没有编程概念的HTML开发者搞糊涂,并且会在HTML编辑器中造成问题。取而代之的是,Struts提供了一种全面的基于JSP 1.1的定制标记库功能的机制来建立表单。上面的情况使用Struts处理后将象是这样:

<struts:text name="username"/>

  没有必要再显式地涉及到从中获得初始值的JavaBean。这将由框架自动处理。

  3.3.1 使用Struts建立表单

  一个完整的注册表单将演示Struts相对于直接使用HTML和标准的JSP功能怎样极大地减轻了处理表单的痛苦。考虑以下称为logon.jsp的页面(来自Struts的例子程序):

<%@ page language="java" %>

<%@ taglib uri="/WEB-INF/struts.tld" prefix="struts" %>

<html>

<head>

<title><struts:message key="logon.title"/></title>

<body bgcolor="white">

<struts:errors/>

<struts:form action="logon.do" name="logonForm"

type="org.apache.struts.example.LogonForm"/>

<table border="0" width="100%">

<tr>

<th align="right">

<struts:message key="prompt.username"/>

</th>

<td align="left">

<struts:text name="username" size="16"/>

</td>

</tr>

<tr>

<th align="right">

<struts:message key="prompt.password"/>

</th>

<td align="left">

<struts:password name="password" size="16"/>

</td>

</tr>

<tr>

<td align="right">

<struts:submit>

<struts:message key="button.submit"/>

</struts:submit>

</td>

<td align="right">

<struts:reset>

<struts:message key="button.reset"/>

</struts:reset>

</td>

</tr>

</table>

</struts:form>

</body>

</html>

  下面的条目基于这个例子演示在Struts中处理表单的关键的特性:

  taglib指令告诉JSP页面编译器从哪里找到Struts标记库的 标记库描述符 。在这种情况下,我们使用struts作为前缀来标识来自这个库中的标记,但是可以使用任何你想用的前缀。

  这个页面使用了几个 message 标记来从一个包含有这个应用程序所有资源的 MessageResources 对象中查找国际化的消息字符串。为了让这个页面能够工作,以下的消息关键字必须在这些资源中被定义:

  logon.title - 注册页面的标题

  prompt.username - 一个 “Username:” 提示字符串

  prompt.password - 一个 “Password:” 提示字符串

  button.submit - “Submit”按钮的标签

  button.reset - “Reset”按钮的标签

  当用户注册时,应用程序可以在用户的session中保存一个 Locale 对象。这个 Locale 将用来选择适当语言的消息。这使得给用户一个切换语言的可选项实现起来变的容易了 -- 仅仅改变保存的 Locale 对象,所有的消息就会自动切换。

  errors 标记显示由一个商业逻辑组件保存的任何出错消息,或者如果没有出错消息保存就什么都没有。这个标记将在下面做深入的描述。

  form 标记基于指定的属性对一个HTML <form> 元素进行处理。它也将所有在这个表单中的字段与一个保存在关键字 logonForm 下的session范围的FormBean相关联。这个bean用来为所有的具有与bean中的属性名匹配的名字的输入字段提供初始值。如果适当的bean没有找到,一个新的bean将会被自动建立,使用指定的Java类名。

  text 标记对一个类型为“text”的HTML <input> 元素进行处理。在这种情况下,占据浏览器屏幕的字符位置的数字也被指定。当页面被执行时,是相对应的bean的 username 属性的当前值(也就是 getUsername() 的返回值)。

  password 标记使用方法类似。不同之处是当用户敲入他们的口令时浏览器将回应星号字符,而不是输入值。

  submit 和 reset 标记在表单低部生成相应的按钮。每个按钮的文本标签使用 message 标记建立,同时带有提示,这样这些值就是国际化的。

3.3.2 输入字段类型支持
Struts为所有以下类型的输入字段定义了标记,带有与其相应的参考信息的超联接。
checkboxes
hidden 字段
password 输入字段
radio 按钮
reset 按钮
select 列表和嵌入的
options
submit 按钮
text 输入字段
textareas
在所有情况下,一个字段标记都必须嵌套在一个 form 标记中,这样字段才知道使用哪个bean来初始化显示的值。

3.3.3 其它有用的表示标记
在Struts的标记库中有几个其它的标记对于建立用户界面是有帮助的:
enumerate 为一个指定集合的每个元素重复一次标记体(可以是一个Enumeration,一个Hashtable,一个Vector或一个对象数组)。
getProperty 从指定的bean中得到指定的属性,并且在本页面的其余部分作为一个page范围的bean存在。这是访问一个被 enumerate 使用的集合的方便的方法。
ifAttributeExists 只有在一个指定的属性存在于一个指定的范围中时才对标记体求值。
ifAttributeMissing 只有在一个指定的属性不存在于一个指定的范围中时才对标记体求值。
ifParameterEquals 只有在一个指定的请求参数具有一个指定的值时才对标记体求值。
ifParameterNotEquals 只有在一个指定的请求参数不具有一个指定的值或者不存在时才对标记体求值。
ifParameterNotNull 只有在一个指定的请求参数包含在这个请求中并且长度大于0时才对标记体求值。
ifParameterNull 只有在一个指定的请求参数不包含在这个请求中或者长度等于0时才对标记体求值。
iterate 为一个指定集合中的每个元素重复一次标记体(可以是一个Collection,一个Iterator,一个Map,或者一个对象数组)。这个标记在Java2环境中代替了 enumerate 标记。
link 生成一个超联接,当没有cookie支持时自动应用URL编程来维护session状态。
parameter 处理指定请求参数的值,适当地过滤HTML中有特殊含义的字符。
property 显示一个表单中命名的bean属性 -- 在属性应该是只读时使用这个标记而不是 text 标记。

3.3.4 自动表单验证
除了上面描述的表单和bean的交互外,如果你的bean知道怎样验证它接收的输入字段,Struts还提供一种附加的机制。为了利用这个特性,使你的bean类实现 ValidatingActionForm 接口,而不是 ActionForm 接口。一个 ValidatingActionForm 增加了一个附加的方法签名:
public String[] validate()
对于一个被controller servlet在bean属性已经组装但是在相应的行为类的 perform() 方法被调用之前调用的方法,validate() 方法有以下可选项:
执行适当的验证发现没有错误 -- 返回 null 或者一个非0长度字符串数组,并且controller servlet将继续调用适当的 Action 类的 perform() 方法。
执行适当的验证发现有错误 -- 返回一个内容为应该被显示的出错消息关键字(进入应用程序的MessageResources 包)的字符串数组。controller servlet将作为适合于 <struts:errors> 标记使用的请求属性保存这个数组,并且将控制重定向回输入表单(由这个 ActionMapping 的 inputForm 属性标识)。
正如以前提到的,这个特性完全是可选的。如果你的form bean 仅仅实现了 ActionForm 接口,controller servlet将假设任何请求的验证由action类完成。

3.4 其它的表示技术
尽管你的应用程序的外表和感觉可以完全基于标准的JSP能力和Struts的定制标记库构建,你也应该考虑展开其它改进组件重用、减少管理负担或者减少出错的技术。在下面的部分讨论几个可选的技术。

3.4.1 特定于应用程序的定制标记
在使用Struts库提供的定制标记之外,很容易建立特定于你创建的应用程序的标记来帮助建立用户界面。Struts包括的例子程序用建立以下仅用于实现这个应用程序的标记演示了这个原则:
checkLogon - 检查一个特殊的会话对象的存在,如果不存在将控制重定向到注册页面。这是用来捕捉这样的情况,用户在你的应用程序执行的中间把一个页面做成书签并且试图跳过注册,或者用户的会话超时。
linkSubscription - 为一个详细的定单页面生成一个超联接,它将需要的主关键字值作为一个请求属性传递。这在列出与一个用户相关的定单并提供编辑或删除定单的联接时使用。
linkUser - 为一个用户的一个具体的页面生成一个超联接,它将它将需要的主关键字值作为一个请求属性传递。
这些标记的源代码在 src/example 目录中,在包 org.apache.struts.example 里,还带有一些其它的用在这个应用程序中的Java类。

3.4.2 有包含文件的页面组件
在一个JSP文件(包含定制标记和beans用来访问请求的动态数据)中创建完整的表示是一种非常普通的设计方法,在Struts包括的例子程序中被采用。然而很多应用程序要求在单独一个页面中显示你的应用程序的多个逻辑上独立的部分。
举例来说,一个入口应用程序可以在入口的主页面上有一些或者全部以下的功能:

访问这个入口的一个搜索引擎。
一个或更多的“提供新闻”的显示,含有按照用户的注册信息定制的感兴趣的标题。
访问与这个入口相关的讨论的主题。
如果你的入口提供免费邮件帐号,还要有一个“邮件等待”的提示。
如果你能够将工作划分开,分配不同的开发者去做不同的片段,那么这个站点不同片段的开发就会更加简单。然后,你可以使用JSP技术的 include 能力来将这些片段组合进一个单独的页面。有两种 include 可用,依赖于你希望输出的组合发生在什么时间:
include 指令 (<%@ include file="xxxxx" %>)在JSP页面被编译时处理。它用于包括不需要在请求时改变的HTML代码。它把包括进来的文本当作静态文本,很象C或C++中的 #include 指令。
include 行为 (<jsp:include page="xxxxx" flush="true" />)在请求时处理,并且是由服务器透明处理。这意味着你可以通过把它嵌套在一个类似ifParameterEquals的标记中有条件地执行include 。

3.4.3 图片处理组件
一些应用程序要求动态生成图片,就象一个股市报告站点的价格图一样。通常使用两种不同的方法来实现这个需求:
处理一个执行一个servlet请求的URL的超联接。这个servlet将使用一个图象库来生成图片,设置适当的content类型(例如 image/gif),并且将图片的字节流发送回浏览器。浏览器就会象从一个静态文件中接收到的一样显示图片。
处理HTML代码需要下载一个创建请求的图象的Java applet。你可以通过为在处理的代码中的这个applet设置适当的初始化参数配置这个图象,或者你可以让这个applet与服务器建立自己联接来接收这些参数。

4. 创建Controller组件
4.1 概述
现在我们理解了怎样构造你的应用程序的Model和View组件,现在是集中到 Controller 组件的时候了。Struts包括一个实现映射一个请求URI到一个行为类的主要功能的servlet。因此你的与Controller有关的主要责任是:
为每一个可能接收的逻辑请求写一个 Action 类(也就是,一个 Action 接口的实现)
写一个定义类名和与每个可能的映射相关的其它信息的 ActionMapping 类(也就是,一个 ActionMapping 接口的实现)
写行为映射配置文件(用XML)用来配置controller servlet。
为你的应用程序更新web应用程序展开描述符文件(用XML)用来包括必需的Struts组件。
给你的应用程序添加适当的Struts组件。
4.2 Action类
Action 接口定义一个单一的必须由一个 Action 类实现的方法,就象下面这样:
public ActionForward perform(ActionServlet servlet,

ActionMapping mapping,

ActionForm form,

HttpServletRequest request,

HttpServletResponse response)

throws IOException, ServletException;
一个 Action 类的目标是处理这个请求,然后返回一个标识JSP页面的 ActionForward 对象,控制应该重定向这个JSP页面以生成相应的响应。在 Model 2 设计模式中,一个典型的 Action 类将在它的 perform() 方法中实现下面的逻辑:
验证用户session的当前状态(例如,检查用户已经成功地注册)。如果 Action 类发现没有注册存在,请求应该重定向到显示用户名和口令用于注册的JSP页面。应该这样做是因为用户可能试图从“中间”(也就是,从一个书签)进入你的应用程序,或者因为session已经超时并且servlet容器创建了一个新的session。
如果验证还没有发生(由于使用一个实现 ValidatingActionForm 接口的form bean),验证这个 form bean 的属性是必须的。如果发现一个问题,当作一个请求属性保存合适的出错信息关键字,然后将控制重定向回输入表单这样错误可以被纠正。
执行要求的处理来处理这个请求(例如在数据库里保存一行)。这可以用嵌入 Action 类本身的代码来完成,但是通常应该调用一个商业逻辑bean的一个合适的方法来执行。
更新将用来创建下一个用户界面页面的服务器端对象(典型情况下是request范围或session范围beans,定义你需要在多长时间内保持这些项目可获得)。
返回一个标识生成响应的JSP页面的适当的 ActionForward 对象,基于新近更新的beans。典型情况下,你将通过在你接收到的 ActionMapping 对象(如果你使用一个局部于与这个映射上的逻辑名)或者在controller servlet 本身(如果你使用一个全局于应用程序的逻辑名)上调用 findForward() 得到一个对这样一个对象的引用。
当为 Action 类编程时要记住的设计要点包括以下这些:
controller servlet仅仅创建一个你的 Action 类的实例,用于所有的请求。这样你需要编写你的 Action 类使其能够在一个多线程环境中正确运行,就象你必须安全地编写一个servlet的 service() 方法一样。
帮助线程安全编程的最重要的原则就是在你的 Action 类中仅仅使用局部变量而不是实例变量。局部变量创建于一个分配给(由你的JVM)每个请求线程的栈中,所以没有必要担心会共享它们。
尽管不应该,代表你的系统中Model部分的的beans仍有可能抛出违例。你应该在你的 perform() 方法的逻辑中捕捉所有这样的违例,并且通过执行以下语句将它们记录在应用程序的日志文件中(包括相应的栈跟踪信息):
servlet.log("Error message text", exception);
作为一个通用的规则,分配很少的资源并在来自同一个用户(在用户的session中)的请求间保持它们会导致可伸缩性的问题。你应该在将控制重定向到适当的View组件前努力释放这样的资源(例如数据库联接) -- 甚至在你调用的一个bean抛出了一个违例时。
另外,你将会想要防止出现非常大的 Action 类。最简单的实现途径是将你的功能逻辑嵌入到 Action 类本身,而不是将其写在独立的商业逻辑beans中。除了使 Action 类难于理解和维护外,这种方法也使得难于重用这些商业逻辑代码,因为代码被嵌入到一个组件(Action 类)中并被捆绑运行于web应用程序环境中。
包括在Struts中的例子程序某种程度上延伸了这个设计原则,因为商业逻辑本身是嵌入到 Action 类中的。这应该被看作是在这个样本应用程序设计中的一个bug,而不是一个Struts体系结构中的固有特性,或者是一个值得仿效的方法。

4.3 ActionMapping实现
为了成功地运行,Struts的controller servlet需要知道关于每个URI该怎样映射到一个适当的 Action 类的几件事。需要了解的知识封装在一个叫做 ActionMapping 的Java接口中,它有以下属性:
actionClass - 用于这个映射的 Action 类完整的Java类名。第一次一个特定的映射被使用,一个这个类的实例将被创建并为以后重用而保存。
formAttribute - session范围的bean的名字,当前的这个映射的 ActionForm 被保存在这个bean之下。如果这个属性没有被定义,没有 ActionForm 被使用。
formClass - 用于这个映射的 ActionForm 类完整的Java类名。如果你在使用对form beans的支持,这个类的一个实例将被创建并保存(在当前的用户会话中)
path - 匹配选择这个映射的请求的URI路径。看下面如何匹配的例子。
Struts在一个叫做 ActionMappingBase 的类中包括了一个 ActionMapping 接口的方便的实现。如果你不需要为你自己的映射定义任何附加的属性,尽管把这个类作为你的 ActionMapping 类好了,就向下面部分描述的那样配置。然而,定义一个 ActionMapping 实现(多半是扩展 ActionMappingBase 类)来包含附加的属性也是可能的。controller servlet知道怎样自动配置这些定制属性,因为它使用Struts的Digester模块来读配置文件。
包括在Struts的例子程序中,这个特性用来定义两个附加的属性:

failure - 如果Action类检测到它接收的输入字段的一些问题,控制应该被重定向到的上下文相关的URI。典型情况下是请求发向的JSP页面名,它将引起表单被重新显示(包含Action类设置的出错消息和大部分最近的来自ActionForm bean的输入值)。
success - 如果Action类成功执行请求的功能,控制应该被重定向到的上下文相关的URI。典型情况下是准备这个应用程序的会话流的下一个页面的JSP页面名。
使用这两个额外的属性,例子程序中的 Action 类几乎完全独立于页面设计者使用的实际的JSP页面名。 这个页面可以在重新设计时被重命名,然而几乎不会影响到 Action 类本身。如果“下一个”JSP页面的名字被硬编码到 Action 类中,所有的这些类也需要被修改。

4.4 Action映射配置文件
controller servlet怎样知道你想要得到的映射?写一个简单地初始化新的 ActionMapping 实例并且调用所有适当的set方法的小的Java类是可能的(但是很麻烦)。为了使这个处理简单些,Struts包括一个Digester模块能够处理一个想得到的映射的基于XML的描述,同时创建适当的对象。看 API 文档 以获得关于Digester更多的信息。
开发者的责任是创建一个叫做 action.xml 的XML文件,并且把它放在你的应用程序的WEB-INF目录中。(注意这个文件并不需要 DTD,因为实际使用的属性对于不同的用户可以是不同的)最外面的XML元素必须是<action-mappings>,在这个元素之中是嵌入的0个或更多的 <action> 元素 -- 每一个对应于你希望定义的一个映射。

来自例子程序的 action.xml 文件包括“注册”功能的以下映射条目,我们用来说明这个需求:

<action-mappings>

<forward name="logon" path="/logon.jsp"/>

<action path="/logon"

actionClass="org.apache.struts.example.LogonAction"

formAttribute="logonForm"

formClass="org.apache.struts.example.LogonForm"

inputForm="/logon.jsp">

<forward name="success" path="/mainMenu.jsp"/>

</action>

</action-mappings>
就象你所看到的,这个映射匹配路径 /logon (实际上,因为例子程序使用扩展匹配,你在一个JSP页面指定的请求的URI结束于/logon.do)。当接收到一个匹配这个路径的请求时,一个 LogonAction 类的实例将被创建(仅仅在第一次)并被使用。controller servlet将在关键字 logonForm 下查找一个session范围的bean,如果需要就为指定的类创建并保存一个bean。
这个 action 元素也定义了一个逻辑名“success”,它在 LogonAction 类中被用来标识当一个用户成功注册时使用的页面。象这样使用一个逻辑名允许将 action 类隔离于任何由于重新设计位置而可能发生的页面名改变。

这是第二个在任何 action 之外宣告的 forward 元素,这样它就可以被所有的action全局地获得。在这个情况下,它为注册页面定义了一个逻辑名。当你调用 mapping.findForward() 时在你的 action 代码中,Struts首先查找这个action本地定义的逻辑名。如果没有找到,Struts会自动为你查找全局定义的逻辑名。

4.5 Web应用程序展开描述符
设置应用程序最后的步骤是配置应用程序展开描述符(保存在文件WEB-INF/web.xml中)以包括所有必需的Struts组件。作为一个指南使用例子程序的展开描述符,我们看到下面的条目需要被创建或修改。
4.5.1 配置Action Servlet实例
添加一个条目定义action servlet本身,同时包括适当的初始化参数。这样一个条目看起来象是这样:
<servlet>

<servlet-name>action</servlet-name>

<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>

<init-p
aram>

<param-name>application</param-name>

<param-value>org.apache.struts.example.ApplicationResources</param-value>

</init-param>

<init-param>

<param-name>config</param-name>

<param-value>/WEB-INF/action.xml</param-value>

</init-param>

<init-param>

<param-name>debug</param-name>

<param-value>2</param-value>

</init-param>

<init-param>

<param-name>mapping</param-name>

<param-value>org.apache.struts.example.ApplicationMapping</param-value>

</init-param>

<load-on-startup>2</load-on-startup>

</servlet>

  controller servlet支持的初始化参数在下面描述,拷贝自 ActionServlet 类的 Javadocs 。方括号描述如果你没有为那个初始化参数提供一个值时假设的缺省值。

  application - 应用程序资源包基类的Java类名。[NONE].

  config - 包含配置信息的XML资源的上下文相关的路径。[/WEB-INF/action.xml]

  debug - 这个servlet的调试级别,它控制记录多少信息到日志中。[0]

  digester - 我们在 initMapping() 中利用的Digester的调试级别,它记录到System.out而不是servlet的日志中。[0]

  forward - 使用的ActionForward实现的Java类名。[org.apache.struts.action.ActionForward]

  mapping - 使用的ActionMapping实现的Java类名。[org.apache.struts.action.ActionMappingBase]

  nocache - 如果设置为 true,增加HTTP头信息到所有响应中使浏览器对于生成或重定向到的任何响应不做缓冲。[false]

  null - 如果设置为 true,设置应用程序资源使得如果未知的消息关键字被使用则返回 null。否则,一个包括不欢迎的消息关键字的出错消息将被返回。[true]

  4.5.2 配置Action Servlet映射

  有两种通常的方法来定义将被controller servlet处理的URL -- 前缀匹配和扩展匹配。每种方法的一个适当的映射条目将在下面被描述。

  前缀匹配意思是你想让所有以一个特殊值开头(在上下文路径部分之后)的URL传递给这个servlet。这样一个条目看起来可以象是这样:

<servlet-mapping>

<servlet-name>action</servlet-name>

<url-pattern>/execute/*</url-pattern>

</servlet-mapping>

  它意味着一个匹配前面描述的 /logon 路径的请求的URL看起来象是这样:

http://www.mycompany.com/myapplication/execute/logon

  这里 /myapplicationis 是你的应用程序展开所在的上下文路径。

  另一方面,扩展映射基于URL以一个跟着定义的一组字符的句点结束的事实而将URL匹配到action servlet 。例如,JSP处理servlet映射到 *.jsp 模式这样它在每个JSP页面请求时被调用。为了使用 *.do 扩展(它意味着“做某件事”)映射条目看起来应该象是这样:

<servlet-mapping>

<servlet-name>action</servlet-name>

<url-pattern>*.do</url-pattern>

</servlet-mapping>

  并且一个匹配以前描述的 /logon 路径的请求的URI可以看起来象是这样:

http://www.mycompany.com/myapplication/logon.do

  4.5.3 配置Struts标记库

  下一步,你必须添加一个定义Struts标记库的条目。这个条目看起来应该象是这样:

<taglib>

<taglib-uri>/WEB-INF/struts.tld</taglib-uri>

<taglib-location>/WEB-INF/struts.tld</taglib-location>

</taglib>

  它告诉JSP系统到哪里去找这个库的标记库描述符(在你的应用程序的WEB-INF目录,而不是在外部互联网上的某个地方)。

  4.5.4 添加Struts组件到你的应用程序中

  为了在你的应用程序运行时使用Struts,你必须将 struts.tld 文件拷贝到你的 WEB-INF 目录,将 struts.jar 文件拷贝到你的 WEB-INF/lib 。

集成 Struts、Tiles 和 JavaServer Faces

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

级别:高级

Srikanth Shenoy (srikanth@srikanth.org), J2EE 顾问, Objectseek Inc.
Nithin Mallya (nithin@mallya.org),J2EE 顾问,Objectseek Inc.

2003 年 10 月

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

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

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

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

为了运行本文中的示例代码,需要 Struts 1.1、Tiles、JavaServer Faces Reference Implementation (JSF-RI) Early Access Release 4.0 以及 Struts-Faces 0.4。Jakarta 项目提供的 Struts 1.1 发行版本将 Struts 和 Tiles 捆绑发布。还可以从 Jakarta 项目上下载 Struts-Faces 集成库。JSF-RI 是 Sun 的 Web 开发工具包(Web Services Developer Pack)的一部分(在参考资料中有这些下载和示例代码的链接)。

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

JSF 简介
JSF 应用程序是使用 JSF 框架的普通 J2EE Web 应用程序,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 提供了一个 render kit,它带有一个自定义标记库,用以从 UI 组件呈现 HTML。它还提供了根据需要定制这些组件外观的能力。如果需要特殊的组件,那么可以为特定的客户机设备构造定制的标记并让它与一个子 UI 组件和定制的 renderer 相关联。对于不同的设备,您所需要做的就是指定不同的 renderer。

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

JSF UIComponentRenderer 和标记总是共同工作的。所有 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 页面
Rendering a JSF page

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 to citiesdeparture return dates,还有提交和重设表单的按钮(我们会在稍后分析清单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 文本字段与一个 JSF Validator 链接起来。

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 表单

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

<f:use_faces>
 <h:form id="flightForm" formName="flightForm" >
   <h:input_text id="fromCity" valueRef="FlightSearchBean.fromCity">
   <f:validate_required/>
   <h:input_text/>

   <h:input_text id="toCity" valueRef="FlightSearchBean.toCity">
   <h:input_text id="departureDate" 
     valueRef="FlightSearchBean.departureDate">
   <h:input_text id="arrivalDate" 
     valueRef="FlightSearchBean.arrivalDate">

   <h:command_button id="submit" action="success" 
     label="Submit" commandName="submit" >
       <f:action_listener 
         type="foo.bar.FlightSearchActionListener"/>
   </h:command_button>
   <h:command_button id="reset" action="reset" label="Reset" 
     commandName="reset" />

   <h:output_errors/>
 </h:form>
</f:use_faces>

在这段代码中使用了两个 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 标记类似于 Struts html:errors 标记,用于显示在表单字段验证中出现的任何错误。

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

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

<managed-bean>
  <managed-bean-name>FlightSearchBean</managed-bean-name>
  <managed-bean-class>
    foo.bar.FlightSearchBean
  </managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

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

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

3. 得到 renderer
下一步,FormTaggetRendererType 方法中得到其 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 与 Struts RequestProcessor 之间没有链接。在 Struts 应用程序中,RequestProcessor 负责用 ActionForm Actions 类中的回调方法显示。ActionForm 属性和 validate() 的 getter 和 setter 是 ActionForm 中的回调方法。对于 Actionexecute() 是回调方法。除非调用了 RequestProcessor,否则 Struts ActionFormActions 类中的回调方法没有机会调用业务逻辑。

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

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

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

图 4. Struts-Faces 类图
单击这里以查看该图。

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

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

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

  • FormComponent 类,它扩展了 JSF Form 组件,但是是在 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-Faces API 所使用。

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

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-faces" 
  prefix="s" %> 

<f:use_faces>
    <s:form action="/listFlights">
     <h:input_text id="fromCity" valueRef="FlightSearchForm.fromCity"/>

     <h:input_text id="toCity" valueRef="FlightSearchForm.toCity"/>
     <h:input_text id="departureDate" 
       valueRef="FlightSearchForm.departureDate">
     <h:input_text id="arrivalDate" 
       valueRef="FlightSearchForm.arrivalDate">

     <h:command_button id="submit" action="success" label="Submit" 
       commandName="submit" />
     <h:command_button id="reset" action="reset" label="Reset" 
       commandName="reset" />

     <s:errors/>
    </s:form>
</f:use_faces>

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 应用程序中这会是<html:text property="fromCity"/>)。

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

<form-bean  name="FlightSearchForm"
               type="foo.bar.FlightSearchForm"/>

<!-- ========== Action Mapping Definition ========================= -->
<action-mappings>

<!-- List Flights action -->
 <action path="/listFlights"
    type="foo.bar.FlightSearchAction"
    name="FlightSearchForm"
    scope="request"
    input="/faces/FlightSearch.jsp">
    <forward name="success" path="/faces/FlightList.jsp"/>
 </action>

</action-mappings>

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

集成 Struts 和 Tiles 的五个步骤
以下五步可以让 Struts 1.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 文件中。这是支持 Tiles 的 Struts 应用程序的默认请求处理程序。

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

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

Struts 应用程序移植到 JSF
为了将 Struts Web 应用程序与 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)以便有一个 Faces Servlet 项, 如清单 5 所示。

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

  • 对于每一个使用 JSF 标记的 JSP,修改 struts-config.xml 文件以在指向该 JSP 的 Action Mapping 中的 global-forwardslocal-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

<!-- JavaServer Faces Servlet Configuration -->
<servlet>
<servlet-name>faces</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<!-- JavaServer Faces Servlet Mapping -->
<servlet-mapping>
  <servlet-name>faces</servlet-name>
  <url-pattern>/faces/*</url-pattern>
</servlet-mapping>

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

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

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

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

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

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

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

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

<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-faces"prefix="s" %>

<!-- Layout component parameters: header, menu, body, footer -->
<s:html>
<head>
  <title> <tiles:getAsString name="title"/></title>
  <s:base/>
</head>
<body>
  <TABLE border="0" width="100%" cellspacing="5">
    <tr>
     <td><tiles:insert attribute="header"/></td>
    </tr>

    <tr>
     <td><tiles:insert attribute="body"/></td>
    </tr>

    <tr><td><hr></td></tr>

    <tr>
     <td><tiles:insert attribute="footer" /></td>
    </tr>
  </TABLE>
</body>
</s:html>


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

<!-- Master Layout definition  -->
<definition name="foobar.master-layout" 
  path="/faces/layout/MasterLayout.jsp">
      <put name="title"  value="Welcome to Foo Bar Travels" />
      <put name="header" value="/faces/common/header.jsp" />
      <put name="footer" value="/faces/common/footer.jsp" />
      <put name="body"   value="" />
</definition>

  <!-- Definition for Flight Search Page -->
<definition name="/foobar.flight-search" 
  extends="foobar.master-layout">
      <put name="body"   value="/faces/FlightSearch.jsp" />
</definition>

响应已经提交
这是您在试图访问航班搜索表单时马上会看到的第一个问题。小心查看堆栈跟踪。您会看到问题出在类 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()方法

RequestDispatcher rd = null;
Tree tree = context.getTree();
String requestURI = context.getTree().getTreeId();
rd = request.getRequestDispatcher(requestURI);

/** If the response is committed, include the resource **/
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 的初始化

//Get the LifecycleFactory from the Factory Finder
LifecycleFactory factory = (LifecycleFactory) 
  FactoryFinder.getFactory("javax.faces.lifecycle.LifecycleFactory");

//Get the context param from web.xml
String lifecycleID = 
getServletContext().getInitParameter("javax.faces.lifecycle.LIFECYCLE_ID");

//Get the Lifecycle Implementation
Lifecycle lifecycle = factory.getLifecycle(lifeCycleID);

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

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

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

//Get the LifecycleFactory from the Factory Finder
LifecycleFactory factory = (LifecycleFactory) 
  FactoryFinder.getFactory("javax.faces.lifecycle.LifecycleFactory");

//Create a new instance of Lifecycle implementation - 
//com.sun.faces.lifecycle.LifecycleImpl
//According to the documentation, factory.getLifecycle("STFLifecycle") 
//should work, but JSF-RI has a defect.
//Hence this workaround of creating a RI class explicitly.
LifecycleImpl stfLifecycleImpl = new LifecycleImpl();

//Create a new instance of our STFViewHandler and set it on the Lifecycle
stfLifecycleImpl.setViewHandler(new STFViewHandlerImpl());

//Register the new lifecycle with the factory with a unique 
//name "STFLifecycle"
factory.addLifecycle("STFLifecycle", stfLifecycleImpl);

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


    <context-param>
        <param-name>javax.faces.lifecycle.LIFECYCLE_ID</param-name>
        <param-value>STFLifecycle</param-value>
    </context-param>

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

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


<listener>
  <listener-class>foo.bar.stf.application.STFContextListener
  </listener-class>
</listener>

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

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

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

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

清单 11. STFRequestProcessor.java

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


<controller processorClass="foobar.stf.application.STFRequestProcessor" />

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

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


<form name="FlightSearchForm" method="post" 
  action="/flightapp/faces/FlightSearch.jsp">

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


<s:form action="/listFlights.do>

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

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

图 5. 呈现 Struts-Faces 响应
单击这里以查看该图。

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

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

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

清单 12. FormComponent 中修改过

下载Hibernate中文参考手册

Google AdSense

Google AdSense是Google提供的投放广告服务,只要别人点击了您网页上的Google AdSense广告,GOOGLE将向您付费,这是个人网站的福音,互联网的春天终于吹到了个人网站的身边,大家还不赶快行动。
注册Google AdSense主要分成3步
1.在google网站上注册这项服务。
    点击下面的连接,点击注册,注册的时候一定要准确的输入您的名字和地址,错了可收不到钱。注册完毕之后请耐心等待,一般google会在一周之后给出确认函(没有办法,现在google这么火,当然效率差点了)。
https://www.google.com/adsense/
2.收到确认函之后您还需要登陆Googlehttps://www.google.com/adsense/,您还需要注册税务信息,有钱赚当然要交税,呵呵。注册之后您就大功告成了。
3.然后在会员区广告设置中设置您所要的google广告的外观了,google提供很多颜色和大小的广告条,可以和您的网站保持一致。

<script type="text/javascript"><!--
google_ad_client = "pub-4944583547581781";//user name
google_ad_width = 728;//width
google_ad_height = 90;//height
google_ad_format = "728x90_as";//format
google_ad_channel ="";
//--></script>
<script type="text/javascript"
  src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>

好困呀

努力的写完了开始的市场分析和需求分析.在8点多的时候睡觉.

11点起床,下午建好了表格,做了数据库词典.

呵呵,进度保持住,就可以了.

实在是撑不住了,偶要去睡觉了.

晕晕的,晚饭的时候把水放的多了,粥不像粥,饭不像饭,真失败.

还好菜的味道还行.