百度空间 | 百度首页 
               
 
查看文章
 
[收录]JavaServer Faces 1.2 入门,第 2 部分(III): JSF 生命周期、转换、检验和阶段监听器
2008-03-06 12:58

JSF 应用程序的生命周期

与许多人认为的相反,即使不了解 JSF 技术的细节,也可以编写 JSF 应用程序;只需通过开发一个项目,就可以学到许多东西。但是,了解某些基础知识会大大促进开发工作并节省许多时间。本节暂时抛开联系人应用程序,谈谈 JSF 请求处理生命周期的六个阶段,看看在每个阶段会发生什么以及各阶段是如何相互连接的。这些内容会为本教程余下部分的工作提供一些背景知识。

JSF 应用程序生命周期的阶段

JSF 应用程序生命周期的六个阶段是:

  1. 恢复视图
  2. 应用请求值;处理事件
  3. 处理检验;处理事件
  4. 更新模型值;处理事件
  5. 调用应用程序;处理事件
  6. 显示响应

这六个阶段是 JSF 处理表单 GUI 的一般次序。这个列表按照每个阶段可能的执行次序和事件处理进行排列,但是 JSF 生命周期并不是固定的。可以改变执行的次序,跳过某些阶段或完全脱离生命周期。例如,如果一个无效的请求值被复制到组件,那么会重新显示当前视图,并可能不执行某些阶段。

还可以选择完全脱离 JSF,比如将处理委托给一个 servlet 或另一个应用程序框架。在这种情况下,可以执行一个 FacesContext.responseComplete 方法调用,将用户重定向到另一个页面或 Web 资源,然后使用请求调度器(从 FacesContext 中的请求对象获得)转发到适当的 Web 资源。也可以调用 FacesContext.renderResponse 来重新显示原来的视图。

最重要的是,在利用生命周期组织您的开发工作的同时不会受其束缚。在需要时可以修改默认的生命周期,而不必担心破坏应用程序。在大多数情况下,您会发现采用 JSF 的生命周期是值得的,因为它非常符合逻辑。

在执行任何应用程序逻辑之前,必须检验表单;在执行检验之前,必须对字段数据进行转换。如果坚持采用生命周期,您就可以集中精力考虑检验和转换的细节,而不必关注请求过程本身的阶段。还要注意,其他 Web 框架也有相似的生命周期;只不过没这么明显。

一些使用 JSF 的开发人员可能从来没有编写过组件或扩展过框架,而其他开发人员的工作却集中在这些任务上。 尽管对于几乎任何项目,JSF 生命周期都是相同的,开发人员可以根据其在项目中的角色参与不同的阶段。如果您主要从事整体应用程序开发,那么可能关注请求处理生命周期中间的几个阶段:

  • 应用请求值
  • 处理检验
  • 更新模型值
  • 调用应用程序

如果您主要从事 JSF 组件开发,那么可能关注生命周期的第一个阶段和最后一个阶段:

  • 恢复视图
  • 显示响应

下面分别讨论一下 JSF 请求处理生命周期的每个阶段,包括事件处理和检验。在开始之前,先看看图 5,图 5 显示 JSF 应用程序生命周期的概况:


图 5. JSF 应用程序生命周期

阶段 1:恢复视图

在 JSF 生命周期的第一个阶段 — 恢复视图 中,通过 FacesServlet servlet 发来一个请求。这个 servlet 检查这个请求并提取出视图 ID(视图 ID 由 JSP 页面的名称决定)。

JSF 框架控制器使用这个视图 ID 为当前视图寻找组件。如果这个视图还不存在,JSF 控制器就创建它。如果视图已经存在,JSF 控制器就使用它。视图包含所有 GUI 组件。

生命周期的这个阶段有三种视图实例:新视图、初始视图和 postback,每种视图的处理方法各不相同。对于新视图,JSF 构建一个 Faces 页面的视图,并将事件处理函数和检验器连接到组件。视图保存在一个 FacesContext 对象中。

FacesContext 存储状态信息,JSF 需要使用这些信息为当前请求管理 GUI 组件的状态。FacesContext 将视图存储在它的 viewRoot 属性中;viewRoot 包含与当前视图 ID 对应的所有 JSF 组件。

对于初始视图(第一次装载页面),JSF 创建一个空视图。在处理 JSP 页面时,填充这个空视图。填充初始视图之后,JSF 直接进入显示响应阶段。

对于 postback(用户返回到以前访问过的一个页面),与页面对应的视图已经存在,所以只需恢复它。在这种情况下,JSF 使用现有视图的状态信息重新构造它的状态。

阶段 2:应用请求值

应用请求值 阶段的目标是获取每个组件的当前状态。首先,必须从 FacesContext 对象获取或创建组件,然后获取它们的值。组件值常常取自请求参数,但是也可以取自 cookie 或请求头。对于许多组件,来自请求参数的值存储在组件的 submittedValue 中。

如果组件的直接事件处理属性是 true,那么值被转换为正确的类型并被检验(在下一阶段中进一步进行转换)。然后,将转换后的值存储在组件中。如果值转换或值检验失败,那么生成一个错误消息并放在 FacesContext 中,在显示响应阶段,这个错误消息与任何其他检验错误一起显示。

阶段 3:处理检验

转换和检验一般发生在处理检验 阶段。组件转换并存储它的 submittedValue。例如,如果字段绑定到一个 Integer 属性,那么值就转换为一个 Integer。如果值转换失败,那么生成一个错误消息并放在 FacesContext 中,在显示响应阶段,这个错误消息与任何其他检验错误一起显示。

在应用请求值阶段之后,发生生命周期的第一次事件处理。在这个阶段,根据应用程序的检验规则检验每个组件的值。检验规则可以是预定义的(JSF 附带的),也可以由开发人员定义。将用户输入的值与检验规则进行对比。如果输入的值是无效的,就将一个错误消息添加到 FacesContext 中,并将组件标为无效。如果一个组件被标为无效,JSF 就跳过其他阶段,进入显示响应阶段,就会显示当前的视图和检验错误消息。如果没有发生检验错误,JSF 就进入更新模型值阶段。

阶段 4:更新模型值

JSF 应用程序生命周期的第四个阶段 — 更新模型值 — 通过更新托管 bean 的属性,更新服务器端模型的实际值。只有绑定到一个组件的值的 bean 属性被更新。注意,这个阶段在检验之后发生,所以可以确信复制到 bean 属性的值是有效的(至少在表单字段级上有效;它们在业务规则级上仍然可能是无效的)。

阶段 5:调用应用程序

在生命周期的第五个阶段 — 调用应用程序 — JSF 控制器调用应用程序来处理表单提交。组件值已经经过转换、检验并应用于模型对象,所以现在可以使用它们执行应用程序的业务逻辑。

在这个阶段,调用您的动作处理方法,比如这个示例应用程序的 ContactController 中的 persist() 方法和 read() 方法。

在这个阶段,还为一个给定的序列或可能的多个序列指定下一个逻辑视图。对于成功的表单提交,可以定义特定的结果并返回这个结果。例如,在得到成功的结果时,将用户转移到下一个页面。为了让这个导航操作起作用,必须在 faces-config.xml 文件中以导航规则的形式为成功的结果创建一个映射。发生导航之后,就进入生命周期的最后一个阶段。JSF 获得从动作方法返回的对象并调用它的 toString() 方法。然后使用这个值作为导航规则的结果。(在第 1 部分 中讨论过导航规则的配置。)

阶段 6:显示响应

在生命周期的第六个阶段 — 显示响应,显示视图和它的所有组件,这些组件都处于当前状态。

图 6 展示了 JSF 应用程序生命周期的六个阶段(包括检验和事件处理)的对象状态图:


图 6. JSF 应用程序生命周期的六个阶段


JSF 数据转换器

转换过程可以确保数据是正确的对象或类型,因此将字符串值转换为其他类型,比如 Date 对象、原始数据类型 floatFloat 对象。可以使用内置的转换器,也可以编写定制的转换器。本节讨论 JSF 的标准转换器,然后详细讨论定制的转换器。

JSF 的标准转换器

JSF 提供了许多标准的数据转换器,而且大多数数据转换是自动发生的。表 1 给出用于简单数据转换的转换器 ID 和对应的实现类。


表 1. 标准的 JSF 转换器
转换器 实现类
javax.faces.BigDecimal javax.faces.convert.BigDecimalConverter
javax.faces.BigInteger javax.faces.convert.BigIntegerConverter
javax.faces.Boolean javax.faces.convert.BooleanConverter
javax.faces.Byte javax.faces.convert.ByteConverter
javax.faces.Character javax.faces.convert.CharacterConverter
javax.faces.DateTime javax.faces.convert.DateTimeConverter
javax.faces.Double javax.faces.convert.DoubleConverter
javax.faces.Float javax.faces.convert.FloatConverter

所以,如果绑定到一个 intInteger,那么会自动执行转换。清单 19 给出联系人管理应用程序的一个组件,它直接绑定到 age#{contactController.contact.age}


清单 19. 绑定到 age:JSF 自动执行转换

<%-- age --%>
<h:outputLabel value="Age" for="age" accesskey="age" />
<h:inputText id="age" size="3" value="#{contactController.contact.age}">
</h:inputText>

JSF 会为所有原始数据类型、包装器、StringEnum 属性执行自动转换。它还会转换日期和数字。数字可以有许多种格式,所以它的转换器允许指定最终用户将使用的格式。对于日期,也是如此。清单 20 演示如何使用 JSF 转换器将日期转换为指定的格式。

尽管 JSF 在默认情况下可以很好地处理原始数据类型等数据,但是在处理日期数据时,必须指定 <f:convertDateTime/> 转换标记。这个标记基于 java.text 包并使用短模式、长模式和定制模式。清单 20 演示如何使用 <f:convertDateTime/> 将用户的生日转换为 MM/yyyy月/年)格式的日期对象。在 java.text.SimpleDateFormat 的 Java API 文档中可以找到模式的列表(参见 参考资料)。


清单 20. 指定日期的格式

<%-- birthDate --%>
<h:outputLabel value="Birth Date" for="birthDate" accesskey="b" />
<h:inputText id="birthDate" value="#{contactController.contact.birthDate}">
<f:convertDateTime pattern="MM/yyyy"/>
</h:inputText>
<h:message for="birthDate" errorClass="errorClass" />


JSF 的定制转换器

如果需要将字段数据转换为应用程序特有的值对象,就需要定制的数据转换,比如:

  • String 转换为 PhoneNumber 对象(PhoneNumber.areaCodePhoneNumber.prefix 等等)
  • String 转换为 Name 对象(Name.firstName.last
  • String 转换为 ProductCode 对象(ProductCode.partNumProductCode.rev 等等)
  • String 转换为 Group
  • String 转换为 Tags

为了创建定制的转换器,必须:

  1. 实现 Converter 接口(也称为 javax.faxes.convert.Converter)。
  2. 实现 getAsObject() 方法,这个方法将字段(字符串)转换为对象(例如 PhoneNumber)。
  3. 实现 getAsString 方法,这个方法将对象(例如,PhoneNumber)转换为字符串。
  4. Faces 上下文中注册定制转换器。

图 7 说明这些步骤在 JSF 应用程序生命周期中的位置:


图 7. 定制转换器 getAsObject()getAsString() 方法


在图 7 中,JSF 在处理检验阶段调用定制转换器的 getAsObject() 方法。在这个方法中,必须将请求字符串值转换为所需的对象类型,然后将这个对象返回给对应的 JSF 组件。在将值返回到视图时,JSF 在显示响应阶段调用 getAsString 方法。这意味着转换器也负责将对象数据转换回字符串。

实现 Converter 接口

这个示例应用程序的 Contact 领域对象与 Group 之间存在多对一关系,与 Tag 之间存在多对多关系。在前面(见 清单 9清单 11),在 ContactController 中定义了从 id 值到领域对象的转换。并不在视图中直接绑定到领域属性,而是在 ContactController 中绑定到 id 字段。如果使用 JSF 转换器,就可以减少许多代码,这会大大简化控制器和视图。

清单 21 和清单 22 分别为 GroupTag 转换器实现 Converter 接口:


清单 21. 实现 Converter 接口的 Group 转换器

package com.arcmind.contact.converter;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.application.FacesMessage;

import com.arcmind.contact.model.Group;
import com.arcmind.contact.model.GroupRepository;

public class GroupConverter implements Converter {

...
}


清单 22. 实现 Converter 接口的 Tag 转换器

package com.arcmind.contact.converter;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import com.arcmind.contact.model.Tag;
import com.arcmind.contact.model.TagRepository;

public class TagConverter implements Converter {

...
}

实现 getAsObject() 方法

下一步是实现 getAsObject() 方法,这个方法将字段(字符串)转换为对象。清单 23 给出 GroupConvertergetAsObject() 方法:


清单 23. GroupConvertergetAsObject() 方法

...
public class GroupConverter implements Converter {

public Object getAsObject(FacesContext facesContext, UIComponent component,
String value) {
GroupRepository repo = (GroupRepository) facesContext
.getExternalContext().getApplicationMap()
.get("groupRepository");
Long id = Long.valueOf(value);
if (id == -1L) {
throw new ConverterException(new FacesMessage(
FacesMessage.SEVERITY_ERROR, "required", "required"));
}
return repo.lookup(id);
}
...

}

注意,GroupConverter 查找 groupRepository 并使用 groupRepository 从存储库中读取 Group。还要注意,它检查值是否是 -1L;如果是这样,就通过抛出一个新的 ConverterException 输出所需的消息。

TagConverter 是相似的。它使用 tagRepository 查找标记值。清单 24 给出 TagConvertergetAsObject() 方法:


清单 24. TagConvertergetAsObject() 方法

...

public class TagConverter implements Converter {

public Object getAsObject(FacesContext facesContext, UIComponent component,
String value) {
TagRepository repo = (TagRepository) facesContext
.getExternalContext().getApplicationMap()
.get("tagRepository");
return repo.lookup(Long.valueOf(value));
}

...
}

这两个转换器都没有进行全面的错误检查。如果存储库对象实际上与数据库或缓存服务器通信,那么可能需要把 getAsObject() 包装在 try/catch 块中,并在数据库出现问题时生成一个严重性为 SEVERITY_FATALFacesMessage — 就像 清单 23 中的 GroupConverter 处理 -1L 的方法一样。


实现 getAsString 方法

JSF 需要显示当前选择的值。这需要调用 ConvertergetAsString 方法。清单 25 给出 GroupConvertergetAsString 方法:


清单 25. GroupConvertergetAsString 方法

...
public class GroupConverter implements Converter {

...

public String getAsString(FacesContext facesContext, UIComponent component,
Object value) {
return value == null ? "-1" : "" + ((Group) value).getId();
}

}

清单 26 给出 TagConvertergetAsString() 方法:


清单 26. TagConvertergetAsString() 方法

...
public class TagConverter implements Converter {

...

public String getAsString(FacesContext facesContext, UIComponent component,
Object value) {
return value == null ? "-1" : "" + ((Tag) value).getId();
}

}

Faces 上下文中注册定制转换器

编写了自己的转换器之后,需要让 JSF 在每次遇到导致 GroupTag 的值绑定时使用这些转换器。这需要在 faces-config.xml 文件中使用 <converter> 元素注册转换器,见清单 27:


清单 27. 在 faces-config.xml 中注册转换器

<converter>
<converter-for-class>
com.arcmind.contact.model.Group
</converter-for-class>
<converter-class>
com.arcmind.contact.converter.GroupConverter
</converter-class>
</converter>

<converter>
<converter-for-class>
com.arcmind.contact.model.Tag
</converter-for-class>
<converter-class>
com.arcmind.contact.converter.TagConverter
</converter-class>
</converter>

清单 27 用 <converter-class> 元素指定转换器类,用 <converter-for-class> 元素指定转换所针对的类。


让转换器处理一组标记

遗憾的是,转换器不能处理泛型,比如 List<Tag>。JSF 不允许对泛型列表进行转换。(它应该可以。Java Persistence API [JPA] 可以用 List<Tag> 定义关系。JPA 和 JSF 1.2 都是与 Java EE 5 同时出现的,所以您会认为它们都支持泛型。)为了解决这个问题,可以使用数组。清单 28 演示如何使用 Tag 数组,而不是 List<Tag>


清单 28. 使用数组代替泛型

public class Contact implements Serializable {
...
private List<Tag> tags;
public Tag[] getTags() {
if (tags != null) {
return tags.toArray(new Tag[tags.size()]);
} else {
return null;
}
}

public void setTags(Tag[] tags) {
this.tags = Arrays.asList(tags);
}

类别:jsf-1.2ri | 添加到搜藏 | 浏览() | 评论 (0)
 
最近读者:
 
网友评论:
发表评论:
姓 名:
网址或邮箱: (选填)
内 容:
验证码: 请点击后输入四位验证码,字母不区分大小写
      

     

©2009 Baidu