[收录]JavaServer Faces 1.2 入门,第 2 部分(III): JSF 生命周期、转换、检验和阶段监听器2008-03-06 12:58JSF 应用程序的生命周期 与许多人认为的相反,即使不了解 JSF 技术的细节,也可以编写 JSF 应用程序;只需通过开发一个项目,就可以学到许多东西。但是,了解某些基础知识会大大促进开发工作并节省许多时间。本节暂时抛开联系人应用程序,谈谈 JSF 请求处理生命周期的六个阶段,看看在每个阶段会发生什么以及各阶段是如何相互连接的。这些内容会为本教程余下部分的工作提供一些背景知识。 JSF 应用程序生命周期的阶段 JSF 应用程序生命周期的六个阶段是: 恢复视图 应用请求值;处理事件 处理检验;处理事件 更新模型值;处理事件 调用应用程序;处理事件 显示响应 这六个阶段是 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 对象、原始数据类型 float 或 Float 对象。可以使用内置的转换器,也可以编写定制的转换器。本节讨论 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 所以,如果绑定到一个 int 或 Integer,那么会自动执行转换。清单 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 会为所有原始数据类型、包装器、String 和 Enum 属性执行自动转换。它还会转换日期和数字。数字可以有许多种格式,所以它的转换器允许指定最终用户将使用的格式。对于日期,也是如此。清单 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.areaCode、PhoneNumber.prefix 等等) 将 String 转换为 Name 对象(Name.first、Name.last) 将 String 转换为 ProductCode 对象(ProductCode.partNum、ProductCode.rev 等等) 将 String 转换为 Group 将 String 转换为 Tags 为了创建定制的转换器,必须: 实现 Converter 接口(也称为 javax.faxes.convert.Converter)。 实现 getAsObject() 方法,这个方法将字段(字符串)转换为对象(例如 PhoneNumber)。 实现 getAsString 方法,这个方法将对象(例如,PhoneNumber)转换为字符串。 在 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 分别为 Group 和 Tag 转换器实现 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 给出 GroupConverter 的 getAsObject() 方法: 清单 23. GroupConverter 的 getAsObject() 方法 ...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 给出 TagConverter 的 getAsObject() 方法: 清单 24. TagConverter 的 getAsObject() 方法 ...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_FATAL 的 FacesMessage — 就像 清单 23 中的 GroupConverter 处理 -1L 的方法一样。 实现 getAsString 方法 JSF 需要显示当前选择的值。这需要调用 Converter 的 getAsString 方法。清单 25 给出 GroupConverter 的 getAsString 方法: 清单 25. GroupConverter 的 getAsString 方法 ...public class GroupConverter implements Converter { ... public String getAsString(FacesContext facesContext, UIComponent component, Object value) { return value == null ? "-1" : "" + ((Group) value).getId(); }} 清单 26 给出 TagConverter 的 getAsString() 方法: 清单 26. TagConverter 的 getAsString() 方法 ...public class TagConverter implements Converter { ... public String getAsString(FacesContext facesContext, UIComponent component, Object value) { return value == null ? "-1" : "" + ((Tag) value).getId(); }} 在 Faces 上下文中注册定制转换器 编写了自己的转换器之后,需要让 JSF 在每次遇到导致 Group 或 Tag 的值绑定时使用这些转换器。这需要在 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); } ©2008 Baidu
JavaServer Faces 1.2 入门
最新推荐文章于 2017-09-29 11:06:33 发布