为保证上述操作顺利进行,在struts-config.xml文件中加入声明,指定由哪一个动作处理器来处理指定的动作:
<action path="/showCategories"
scope="request"
type="com.strutsdemo.ShowCategoriesAction"
unknown="false"
validate="false">
<forward name="viewCategories" path="/
ShowCategories.jsp"/>
</action>
|
表单处理
表单处理过程充分体现出Struts的优势。在Web应用中,大部分复杂的HTML处理任务都涉及到表单。表单编辑过程具有类似图5所示的请求或应答结构。更新操作的过程与创建操作的过程相似,但对于更新操作来说,“创建Model”这一步骤变成“装入Model”,而“保存Model”变成了“更新Model”。请注意Web应用的特点:操作过程随时可能中止,这既可能是因为用户通过显式的动作取消了当前的操作,也可能是因为用户没有提交表单,例如用户跳转到了一个不是用来处理当前表单的URL。
图5 表单处理流程
表单编辑过程分三个阶段:这里分别称之为准备(Preparation)、表现(Presentation)和存储(Preservation)。准备和存储阶段都属于Struts动作,而表现阶段主要是客户端的活动。表1显示了该过程中涉及的各种部件:
表单Bean(Form Bean)是一种特殊的JavaBean类型,它简化了表单处理。Form
Bean从org.apache.struts. action.ActionForm类扩展而来。Form
Bean有几个有用的特点,例如,通过reset()方法可以把Bean的属性设置成默认值,通过validate()方法让Bean验证属性的合法性。更重要的是,ActionServlet确保Form
Bean被创建且可供它的动作方法调用。HTML标记库还能够确保Form
Bean被正确地初始化并从Form View获取数据。
Form Bean应当属于Model部分,然而,由于它有validate()方法,因此从某些特征来看它更接近分配器。不过,不必太在乎这些概念上的问题。Model
2并不完全等同于MVC,而且一些人已经在责难MVC不外乎是几种简单设计模式的混合物。不管怎样,从应用实践的角度来讲,系统的稳定性远比概念的严格性更重要。在本例中,这个问题更加富有代表性,因为我们把持久化机制也包装到Form
Bean里面。从技术上看,Bean数据的持久化副本就是一个View,因此,从这个意义上来讲,我们现在有了一个结合了分配器和View特点的Model。这种设计方式看起来似乎否定了引入Struts之类框架的理由,但实际上,这种设计方式两方面的特点弥补了许多遗憾。
首先,由于验证代码和SQL代码在很大程度上依赖于Form
Bean拥有的属性,所以把它们作为一个单元管理会带来很大的方便。由于这里只对Form
Bean的属性感兴趣,“重量级”的分配器和View部件都得到了有效的隔离。其次,Form
Bean与HTML标记库一起使用时,Form Bean可以包含其他对象。这些对象可以通过“.”符号应用。使用预定义的Java对象时,“.”引用方式能够带来很大的方便,因为Java不支持多重继承。“.”引用方式避免了手工编写大量get/set代码的繁杂工作。
当内部对象是EJB时,“.”引用方式带来的方便更加突出,因为在JSP页面中引用EJB时,EJB往往显得很“笨重”。如果EJB嵌入到了Form
Bean里面,许多这方面的遗憾就不再存在。更重要的是,它分离了Controller和Model,而且View持久化也简缩到了最简单的程度,因为EJB容器可以处理所有持久化方面的细节。这样,Form
Bean就几乎成了一个纯粹的分配器,一切都变得整洁和清晰。
如果EJB有大量的属性,而且按照ActionServlet通常对Form
Bean所做的那样,按照每个属性分别更新的方式进行更新,就会出现大量的RMI调用开销。对于要求较高的应用,更好的选择是利用EJB
2.0本地接口,或者在EJB之前加上一个传统的JavaBean(通常是会话EJB),并把该Bean传递给实体Bean的UpdateAllProperties()业务方法。后面这种方案允许在单个RMI调用中完成所有的更新操作。
准备阶段
一次典型的编辑会话要求有一个动作处理器准备View,即一个作为View的JSP页面,还要求有第二个动作处理器存储更新后的View。当然,存储操作之后会有第二个属于View的页面被显示,例如一个“数据已经更新,点击此处继续”的页面(参见表1)。
表1:基于Form Bean的编辑过程要用到的部件
部件 |
说明 |
CatalogForm |
Form Bean |
EditCategoryAction |
准备阶段 |
EditCategory.jsp |
编辑 |
SaveCategoryAction |
存储阶段 |
EditDone.jsp |
确认数据已经保存 |
EditFailed.jsp |
“数据没有保存”错误 |
下面的代码片断显示了如何在struts-config.xml文件中配置准备阶段:
<action path="/editCategory"
scope="request"
name="catForm"
type="com.strutsdemo.EditCategoryAction"
unknown="false"
validate="false">
<forward name="success"
path="/EditCategory.jsp"/>
</action>
|
在准备阶段,容器尝试从Session或Request找出指定的Form
Bean,这是因为在动作中指定了“name=...”。ActionServlet在struts-config.xml文件的
区域寻找Form Bean的别名,利用Form Bean的别名寻找对应的Java类。如果用户的请求带有参数,其名字匹配Form
Bean属性名字的参数将被设置为属性值。Struts扩展了“属性名字”的含义,使得访问Form
Bean内嵌对象的属性成为可能。本文的例子也用到了Struts的这一优点。
准备好Form Bean之后,ActionServlet接着调用动作的process()方法,Form
Bean作为参数之一传入process()方法。在这里,我们对Form
Bean的属性作最后的调整,调用业务方法,委派作为View的EditCategory,从而生成一个以Form
Bean中合适数据为基础的HTML页面。这个页面被传递给客户端,接下来就进入了“表现”阶段。
表现阶段
这一阶段用户编辑表单并提交。如果服务器端的应用认为用户提交的内容存在问题,它把表单再次显示给用户,加上适当的提示信息;重复该过程,直至用户提交了合法的表单,或取消了表单处理过程。编辑过程的中止可能是由于用户跳转到了其他页面,或者启动了一个取消动作(例如点击了一个由html:cancel标记定义的按钮)。虽然在理论上,View的验证和再次显示操作应该属于表现阶段,但在Struts应用中,这部分功能在存储阶段实现最方便。
存储阶段
准备阶段创建了一个带有“name=”属性定义的动作CatForm,存储阶段要加入另外两个属性,即:“validate=‘true’”和“input=”属性。
<action path="/saveCategory"
scope="request"
name="catForm"
type="com.strutsdemo.SaveCategoryAction"
unknown="false"
input="/EditCategory1.jsp"
validate="true">
<forward name="success"
path="/CategoryUpdated.jsp"/>
</action>
|
设置了“validate=‘true’”属性选项之后,服务器端就会增加一个处理步骤。重新用来自View的数据构造出Form
Bean,或更新From Bean的时候,Form Bean的validate()方法会被调用。validate()方法执行必要的合法性验证操作。如果用户的输入数据中存在错误,validate()方法就创建一个或多个ActionError对象。这些ActionError对象包含了错误信息源ID和表单输入域的名称。这些ActionError对象被收集和整理到一个ActionErrors对象,随后ActionErrors对象由validate()方法返回。如果用户输入的数据不包含错误,validate()返回null。
由于指定了“input=”属性,一旦出现了错误,动作会被忽略,而“input=”指定的View被显示。这个View既包含Form
Bean,也包含当前出现的错误对象集合。一般地,这个输入页面就是原来执行编辑功能的JSP页面。
大多数Struts的html标记有对应的HTML标记,但Struts有一个HTML没有的标记,即
标记。要中止表单编辑过程,用户既可以手工输入URL,也可以点击不指向存储动作处理器的链接。因此,用
标记定义的“取消”按钮,不是取消编辑操作的唯一方法。
假设validate()方法没有发现任何错误,且用户没有点击“取消”按钮,存储动作的process()方法将被调用。在本例的process()方法中,我们调用了Form
Bean的save()方法把数据写入持久性存储设备,然后根据写入操作是否成功,显示“存储操作成功”或“存储操作失败”的View。
|