1  JSF请求处理生命周期的高度概述

从历史上看,Web应用程序必需的大部分开发,主要是处理Web客户端的HTTP请求。随着Web从传统的静态文档传送模型(在这种模型中,只请求静态Web页面,没有参数)转变到动态环境(要求Web应用程序处理大量进入参数),对日益复杂的请求的处理需求,不可避免地增长起来。这使Web应用程序开发变得相当烦琐。例如,请看下面在Java servlet或JSP小程序中处理进入请求参数firstnamelastname的代码:

String firstname = request.getParameter("firstname");

String lastname = request.getParameter("lastname");

// Do something with firstname and lastname

考虑到现在多数高级Web应用程序即使不处理几千个,也要处理上百个参数,就可以看出上面这种参数处理方法很容易就会变得很烦琐。

有经验的Web开发人员都知道,编写处理进入请求参数的代码通常包含以下步骤:

  ●    编写对进入数据执行验证并把进入数据转换成服务器端数据类型的代码,并在验证/转换失败时初始化错误消息。

  ●    编写用新数据更新服务器端数据的代码。

  ●    编写调用服务器端应用程序、执行数据库查询之类任务的代码。

  ●    编写把响应渲染回客户端的代码。

幸运的是,请求处理生命周期可以用一种连贯的、基于事件的方式处理这项工作。

专家组意见  在专家组讨论的JavaServer Faces全部元素中,请求处理生命周期得到的贡献最多,参与的成员最广,而且是从1.0规范开发之后,发展最多的元素。例如,最初要求把view描述放在JSP页面之外的独立XML文件中。因为发明了ViewHandler类所以撤销了这个要求,ViewHandler类就是在讨论生命周期的过程中产生的。

 请求处理生命周期到底做什么

简而言之,过去必须自行编写代码才能处理的必要的后端处理,现在全由请求处理生命周期执行。除了处理进入的请求参数,它还管理服务器端的用户界面组件集,并把它们与用户在客户端浏览器中看到的组件同步。

3  请求处理生命周期与其他Web技术的区别

对比其他更传统的Web技术(从CGI、Java servlet到Struts这样的框架),请求处理生命周期用定义良好的基于事件的方式自动执行了大部分常见服务器端Web开发任务。

使用Jakarta Struts这样的框架,用带有表单bean和Struts动作的代码把一些请求处理做得更正规,但实际的数据处理仍在较低层次(与JSF相比)上进行。Struts编程模型对servlet API提供的抽象比JSF提供的少。例如,在Struts中,可以这样定义代表提交表单属性的表单Bean:

 

 

 

 

定义之后,可以像下面这样在应用程序中访问字段值:

String userid = (String)((DynaActionForm)form).get("userid");

这很像能用JSF所做的事情,但在使用Struts时,没有把字段属性直接绑定到Java类属性并让属性的值在表单提交时自动同步的能力。

 自动服务器端视图管理和同步

如图3-1所示,JSF请求处理生命周期能把服务器端Java Bean属性自动同步到有层次的组件集(根据呈现给客户端用户的用户界面)的能力,是它与其他Web技术相比的主要优势。

由于Web天生是无状态的,即客户端与服务器之间的一个事务对前一个事务没有记忆,所以JavaServer Faces通过自动维护代表客户端当前状态的服务器端视图(view)而解决了这个问题。这允许JSF开发人员把精力集中在服务器端组件,由请求处理生命周期或“衔接”(plumbing)负责服务器端视图的同步和在客户端浏览器上显示什么。编写代码处理每个请求值或者修改用户界面状态的烦琐工作,都通过一组阶段(phase)(每个阶段中,都用连贯的方式执行具体的数据处理任务)由JavaServer Faces请求处理生命周期自动处理

 转:JSF请求处理生命周期的高度概述)

 

3-1  客户端用户界面的服务器端表示

 请求处理生命周期阶段是什么

处理进入的请求数据,通常需要不同类型的工作,包括检查进入的数据是否有效、触发服务器端的应用程序逻辑以完成请求,以及最后把响应渲染给客户端。JSF请求处理生命周期用前后连贯的顺序执行这些任务,而且在一组定义良好的阶段的控制之下。这种方式允许每个阶段清晰地描述执行本阶段之前需要存在的前提条件,以及本阶段执行之后会存在的后置条件。

下面是生命周期的各个阶段。

  ●    恢复视图:在内存中恢复或创建代表客户端用户界面信息的服务器端组件树(视图)。

  ●    应用请求值:用来自客户端的最新数据更新这些服务器端组件。

  ●    处理验证:对新数据执行验证和数据类型转换。

  ●    更新模型值:用新数据更新服务器端模型对象。

  ●    调用应用程序:调用满足请求所需要的应用程序逻辑,然后如果有需要,再导航到新页面。

  ●    渲染响应:把响应渲染给请求客户端。

3-2显示了这些阶段合在一起构成的请求处理生命周期的高级视图。可以看到,它执行了Web应用程序中的对进入数据进行处理的全部任务。贯穿本章和本书的其余部分,将介绍这个图中的不同事件和阶段。

现在来深入研究每个生命周期处理阶段中到底发生了什么。

转:JSF请求处理生命周期的高度概述)

 

3-2  JavaServer Faces请求处理生命周期

6 恢复视图

前面提到过,Faces视图是用户界面组件的服务器端树,它提供了在客户端显示的用户界面的镜像表示(请参见图3-3)。恢复视图阶段的任务是根据前一个事务恢复现有视图,或者根据新请求创建新视图。如果是新请求(“没有回传”),就创建新视图,并保存在父容器对象FacesContext内。FacesContext充当与当前请求有关的数据在通过整个请求处理生命周期过程中的存储。Web开发人员不必担心多个用户请求会把FacesContext中的应用程序数据混淆,因为servlet API保证请求操作是线程安全的,即所有对FacesContext的操作,都保证是在一个独立线程上针对每个用户请求进行的。

转:JSF请求处理生命周期的高度概述)

 

3-3  服务器端用户界面组件树(又称作“视图”)

7 应用请求值

恢复了视图之后的下一阶段——应用请求值阶段——做的是对进入的请求值或信息名称-值对进行处理。视图层次结构中的每个用户界面组件节点,现在都能得到客户端发送来的更新值,如图3-4所示。

转:JSF请求处理生命周期的高度概述)

 

3-4  应用请求值

在幕后,JSF运行时在用户界面组件树的视图(或UIViewRoot)上调用高级方法(processDecodes( )),把请求值应用到用户界面组件。这导致所有子组件都递归地调用它们的processDecodes( )方法。在第10章将会看到,用户界面组件的processDecodes( )方法或者更具体的decode( )方法,允许组件对进入的请求名称-值对进行“解码”,并把匹配的新进入值应用到用户界面组件的value属性。

应当指出,只有能够容纳值的用户界面组件(例如输入字段)才有新值应用到它们。一般来说,有两类组件:一类是有值的组件,例如文本字段、复选框和标签;另一类是引起动作的组件,例如按钮和链接。所有具有value属性的组件都实现ValueHolder接口。所有表单元素类型的组件,如果它的值可以由用户编辑,那么就都实现EditableValueHolder接口。所有引起动作的组件(按钮或链接)都实现ActionSource接口。

JSF1.2提示  对于JSF 1.2,动作组件实现的是ActionSource2接口,而不是ActionSource。ActionSource2扩展自JSF 1.1的ActionSource,它允许使用1.2中新的统一EL。关于1.2中统一EL的细节,将在第4章提供。

例如,按钮(UICommand或其他实现ActionSource的组件)在表单提交期间,不用新值更新;它只需记录自己是否被单击。如果单击了,就引起称为动作事件(ActionEvent)的事件进入队列。稍后就会看到动作事件到底是什么,以及它如何允许与按钮或链接单击相对应的定制代码的执行。

虽然请求处理生命周期用连贯的方式处理不同阶段,但阶段的执行顺序可以为特殊情况而变化。例如,可能想向表单添加一个Cancel按钮。在单击时,它会跳过所有验证,不处理表单的值,直接导航到另一个页面。要改变请求处理生命周期的处理顺序,只需设置组件的immediate属性。本章后面将会介绍,在不同的组件上设置immediate属性会有不同的效果。

8.处理验证

应用请求值阶段完成之后,就要执行对进入数据进行转换和验证的处理验证阶段(第7章将详细解释数据类型转换和验证)。JSF运行时调用主processValidators( )方法(在UIViewRoot实例上),启动这个阶段。processValidators( )processDecodes( )方法类似,也是递归地进入组件树,调用每个组件的processValidators( )方法。在调用每个组件的processValidators( )方法时,与组件相关的转换器或验证器就会被调用。

注意  数据类型转换实际是发生在验证之前,但是在同一处理验证阶段内开始。这是必需的,因为要执行验证,数据首先必须转换成服务器端数据类型。

在第2章的JSFReg示例中看到过,验证器和转换器可以用若干种方式与用户界面组件关联。在示例中,验证请求可以通过设置组件本身的属性(例如把inputText组件的required属性设置为“true”)与组件关联,也可以通过注册定制验证代码(例如通过设置组件的validator属性,向组件附加电子邮件验证方法)与组件关联。第2章的示例也有一个转换器,通过插入convertDateTime转换器标签作为输入组件的子组件,与“出生日期”(dob)inputText(UIInput)组件关联。

验证(或转换)失败的组件的valid属性会设置成“false”,并把FacesMessage消息放进FacesContext队列,如图3-5所示。然后,当响应被渲染回用户(在渲染响应阶段)时,可以用Faces的MessageMessages组件显示消息,以便用户纠正错误或重新提交。

转:JSF请求处理生命周期的高度概述)

 

3-5  处理验证阶段中遇到验证和转换错误

9.更新模型值

假设进入的数据已经通过验证和转换,现在要把数据分配给值绑定到用户界面组件的模型对象。回忆一下第2章的示例。在这个示例中,我们创建了一个Java类UserBean,把它注册成托管bean,并用JSF表达式语言把属性绑定到页面上的不同用户界面组件。正是在更新模型值阶段,实际的托管bean或模型对象的属性,被它们绑定的用户界面组件的新值更新。

这个操作背后实际的机制与其他阶段相似:在UIViewRoot实例上调用processUpdates( )方法,初始化processUpdates( )组件方法的逐级调用,processUpdates( )方法又对每个类型为UIInput或者从它扩展的组件调用updateModel( )方法。这么做是合理的,因为UIInput类型的组件(例如输入字段、选择菜单)是能够把用户输入值传递给模型属性的唯一组件类型。如图3-6所示,在这个阶段末尾,模型对象(托管bean)的值绑定属性都被来自组件的新值更新。这个阶段揭示了JavaServer Faces的一部分秘密:只要把JavaBean属性绑定到一组JSF用户界面组件,不需要手动编码,它们就能自动更新。

转:JSF请求处理生命周期的高度概述)

 

3-6  在更新模型值阶段更新模型对象属性

10.调用应用程序

现在,已经看到了JSF请求处理生命周期如何执行从Web请求获得进入数据、验证数据/把数据转换成合适的服务器端数据类型,以及把数据分配给模型对象等工作。对Web开发人员来说,这只是Web应用程序编写工作的一半。另一半是:取得进入数据,真正对数据进行操作,例如调用处理数据的外部方法。这正是调用应用程序阶段的作用。

回忆本章前面所述,用户界面组件既可以容纳值(实现EditableValueHolder)也可以是ActionEvent来源(实现ActionSource)(例如在单击按钮(UICommand)时)。定制动作代码,又称为动作方法动作侦听器方法就是在调用应用程序阶段调用的。

幕后,在调用应用程序阶段中,UIViewRootprocessApplication( )方法被调用,它又把队列中这个阶段的事件广播到每个实现ActionSource(对JSF 1.2是ActionSource2)的UIComponent组件。这是通过调用每个UIComponentbroadcast( )方法做到的,实际上就是“触发了动作事件,接着就由动作侦听器处理这些动作事件。可以用默认动作侦听器编写定制动作方法或动作侦听器方法并把它们绑定到UIComponent(实现ActionSourceUIComponent),处理动作事件。编写定制动作方 法或动作侦听器方法,并把它们绑定到实现ActionSourceUIComponent,为开发人员提供了参与请求处理生命周期的钩子(hook),通过钩子,开发人员就能调用任何定制逻辑。图3-7演示了这一点。

转:JSF请求处理生命周期的高度概述)

 

3-7  在调用应用程序阶段调用定制应用程序逻辑

8章将重新研究JSF事件模型到底如何工作,并提供关于Faces事件处理确切顺序的更多细节。

还应当指出,导航到不同页面,也发生在调用应用程序阶段。第5章将介绍一个基本的登录应用程序,研究导航到底是怎么发生的。登录应用程序使用了一个绑定到login(UICommand)按钮的简单动作。当用户单击按钮时,触发动作事件,动作事件又在调用应用程序阶段调用定制动作方法,处理登录凭据。请记住,这个代码只有在进入的数据已经通过前面阶段(转换和验证已经完成)之后才会执行。登录成功时,会导航到新页面。

11.渲染响应

现在到了JSF请求处理生命周期的最后阶段,在这个阶段渲染响应。为把整个响应渲染回客户端,又一次逐级对每个组件调用encodeXX( )方法。在第10章将学习到,编码方法是用户界面组件(或者更具体点,组件的渲染器)向客户端渲染组件的方法。渲染的标记语言可以是任何语言,例如HTML、WML、XML等等。

除了把响应渲染给客户端,渲染响应阶段还保存视图的当前状态,以便可以在后续Web请求中访问和恢复状态。图3-8演示了如何用客户端标记渲染响应。这时,视图的当前状态被保存下来,供未来请求之用。

另外,实际还有更多精细的幕后细节与渲染响应阶段有关,但本章介绍的范围之内。其中包括:处理静态内容(又称为“模板”源)与组件的动态内容交织的情况;处理各种动态输出源;在保持顺序正确的同时把它们协调成一个可视的响应。一般来说,使用JSF时不需要处理这些细节。

转:JSF请求处理生命周期的高度概述)

 

3-8  在渲染响应阶段渲染响应并保存状态

JSF1.2提示  渲染响应阶段在JSF 1.2中被显著地重新设计了,以解决由于HTML、JSP定制标签和JSF组件在一个页面中混杂而引起的一些棘手问题。这些问题的主要诱因,是“为了渲染而执行JSP页面,而且在执行页面的同时构建组件树”这一要求。这要求JSP和JSF都要写入同一输出流,有时会造成渲染输出以意外顺序出现。JSF 1.2中的新方法是:只为构建树这个唯一目标执行页面,而不为渲染页面执行它。页面中的原始HTML/标记和JSP定制标签输出被转化成瞬态UIOutput组件(不在页面状态中保存)。这样,页面执行之后,页面的整个内容就表示在视图层次结构中,视图又被递归遍历,就像其他生命周期阶段一样

12  实际观察请求处理生命周期

看过每个请求处理生命周期阶段背后的理论之后,是时候看看生命周期的实际作用了。回顾第2章所示的JSF注册(JSFReg)应用程序(如图3-9所示)。我们要作为与应用程序交互并提交注册表单(register.jsp)的用户,经历全部生命周期阶段。

1)查看register.jsp页面的初始请求:为了第一时间看到注册表单页面,用户需要直接发送对注册页面URL的请求,用精简方式触发请求处理生命周期的一次运行。请求首先由Faces控制器servlet处理,控制器为这个请求创建一个FacesContext实例,并初始化对生命周期的调用。由于这是查看注册页面的初始请求(也叫做“无回传”请求),恢复视图阶段创建一个空视图(UIViewRoot)组件树,并把它保存在FacesContext实例中。

转:JSF请求处理生命周期的高度概述)

 

3-9  register.jsp:JSFReg应用程序的注册页面

视图创建之后,因为在请求中没有进入字段数据需要验证或处理(也叫做“无回传”请求),生命周期立即前进到渲染响应阶段。在这个阶段,空视图组件树由注册页面源代码中引用的组件(显示输入字段和提交按钮)填充。树填充之后,组件就把自己渲染到客户端。同时,保存视图组件树的状态,供后续请求使用。用户现在看到在浏览器中渲染的注册页面。

2)用户在注册页面中输入无效数据:假设用户忘记输入了自己的姓,还输入了格式错误的日期,就单击了Register按钮(如图3-10所示)。

转:JSF请求处理生命周期的高度概述)

 

3-10  在注册页面输入错误数据

JSF运行时接收到请求时,进入初始的恢复/创建视图阶段,这次它恢复刚才在用户前一个请求之后保存的视图组件树。这通常叫做“回传”,因为这个请求的HTTP方法是POST(因为要“传送”新表单数据)。然后进入应用请求值阶段,即使请求中的进入值不完全有效,也用请求中的进入值更新组件。在这个阶段没有错误发生,因为每个用户界面组件只是把请求参数的提交值保存为String值,还不是转换后的(服务器端数据类型)值或验证过的值。用户界面组件把这个值保存在一个特殊的经过预转换/预验证的submittedValue JavaBean属性中,它只是原样保存请求参数的String值。

应用请求值阶段完成后,启动处理验证阶段。这时,由于进入的日期数据格式无效,无法转变成java.util.Date数据类型(对应着托管bean UserBean“dob”属性),发生转换错误。一条消息被放入队列,组件被标记为无效,处理继续进行。余下的有效字段值被应用到对应的用户界面组件。

在每个用户界面组件调用它的验证方法时,要容纳lastName值的组件遇到验证错误,因为在回传请求中没有为它提供值。还记得前面姓氏输入字段的“required”属性设置为“true”。作为验证错误的结果,生命周期把UIInput组件lnamelast name)的属性设置为无效,并把对应的表示这个字段必须有值的Faces消息放入队列。这时,处理验证阶段完成。

因为有验证和转换错误,所以生命周期直接跳到渲染响应阶段,然后渲染同一个注册页面(register.jsp),把对应的错误消息放在last name字段和日期字段旁边。回想一下,每个消息组件到每个输入字段组件的分配,是通过分配它们的ID实现的,如下所示:

 

 

除了把响应渲染给客户端,渲染响应阶段也保存视图组件树,供后续请求使用。

3)用户纠正验证错误,重新提交表单:在响应中看到错误消息后,用户对表单进行纠正,提供姓氏并输入格式正确的日期值,然后重新提交。这次,在处理请求时,恢复视图阶段恢复保存的视图树,并前进到应用请求值阶段,在这个阶段把新值应用到视图组件树。阶段转移到处理验证阶段,这次在这个阶段没遇到验证或转换错误。然后触发更新模型值阶段。这时,托管bean(UserBean)的属性被表单中提交的新值更新,如图3-11所示。

转:JSF请求处理生命周期的高度概述)

 

3-11  用验证后的值更新UserBean模型对象

更新模型值阶段完成之后,触发的下一阶段是调用应用程序阶段。还记得调用应用程序阶段为JSF开发人员提供了调用定制逻辑的途径。例如,应用程序可能需要执行查询数据库的代码。执行定制逻辑是通过动作方法实现的,动作方法在调用应用程序阶段被调用,但只有在队列中有动作事件时(例如单击了按钮或链接)才调用。在JSFReg示例中,在单击Register按钮时,一个动作事件被放入队列,但是由于Register按钮(UICommand组件)的action属性硬编码成文字值“register”,所以没有调用定制动作方法,调用应用程序阶段完成。这时,导航处理程序处理动作事件,出现一个导航。然后在最终的渲染响应事件中显示导航事件的结果。

现在再来看一遍:注册页面成功通过验证、UserBean托管bean用表单提交的值更新之后,生命周期到了渲染响应阶段,这时需要把格式化的响应发送回用户。这时,必须确定要发送回客户端的响应。正如在第5章中会更全面介绍的那样,导航处理程序负责确定是否只是用同一页面进行响应,还是导航到新页面。还记得JSFReg示例有一个action属性硬编码成“register”,由于它与faces-config.xml文件中定义的导航规则对应,所以渲染的响应是要导航到新页面(confirm.jsp)。请记住,如果没有设置导航按钮的action属性,或者没有对应的导航规则,那么渲染的响应就会是同一页面,不会出现导航。图3-12显示了NavigationHandler如何利用faces-config.xml中定义的规则确定是否需要导航。

注意  关于Faces导航模型的详细说明,将在第5章提供。

 

转:JSF请求处理生命周期的高度概述)

 

3-12  在调用应用程序阶段,根据动作的结果渲染响应

13  与请求处理生命周期有关的高级主题

看过JSF请求处理生命周期在默认情况下对基本表单的处理方式,现在来看些稍微复杂的示例。

14  使用immediate属性

这一节介绍Faces的一项极为重要的特性:immediate属性,它允许更灵活的生命周期执行方式。

立即处理动作事件

假设想在注册页面上添加一个Cancel按钮,单击这个按钮时,不论表单上输入了什么,都会立即把用户路由回main.jsp页面。要实现这个功能,必须添加一个返回主页面的导航规则,还要在页面上添加一个按钮或链接组件,组件的动作设置为导航规则“cancel”的from-outcome。下面是faces-config.xml中的新导航规则:

 

  /register.jsp

   

      cancel

      /main.jsp

   

 

下面是新添加到Register页面的Cancel按钮,它的action硬编码设置成“cancel”:

 

但是,如果就此停止,那么很快就会遇到问题!如图3-13所示,如果想再次运行应用程序,但是立即单击Cancel按钮,就会卡在那里。出现这个问题是因为即使刚才用空表单单击Cancel按钮,仍然把它当作“回传”请求进行处理,这意味着JSF生命周期开始进行处理,而且假定从表单传来了进入的名称-值对。但是由于表单实际是空的,所以在导航处理程序能处理“cancel”动作事件并把同一页面响应渲染回客户端之前,先遇到了验证错误。显然,需要有一个在正常生命周期处理流程之中允许异常的解决方案。JSF在它的immediate属性中提供的就是这个功能。

转:JSF请求处理生命周期的高度概述)

 

3-13  因为验证错误而卡在页面上

如果给Cancel按钮(或任何UICommand组件)添加了immediate属性,并把它的值设置成“true”,就能允许生命周期立即跳过验证,导航回main.jsp页面。一般来说,在UICommand组件上把immediate属性设置为“true”,会引起动作事件在处理验证阶段之前,在应用请求值阶段就立即触发,这样就不会遇到验证错误。这样就有了把生命周期“短路”的效果,可以避免验证,取消更新任何模型的值。

15  立即处理验证和转换

实现EditableValueHolder接口的组件(像输入字段)带有选项,可以根据immediate属性提供的值,让它们的验证和转换在应用请求值阶段或通常的处理验证阶段立即执行。如果值是“true”,那么该组件的验证和转换立即发生。更重要的是,把immediate属性设为“true”,允许在进入处理验证阶段前用新验证的值更新组件,余下的没有immediate的组件的验证会在处理验证阶段发生。这通常在执行只针对用户界面的修改时有用,例如在不提交和验证整个表单数据的情况下就启用输入字段,使字段变成可编辑。

在第8章,会发现如何用immediate属性和值修改侦听器在不验证整个表单内容的情况下,修改用户界面属性。

专家组意见  immediate属性最初只在ActionSource接口上有,后来添加到EditableValueHolder接口,以便支持这类场景:输入组件想严格控制在用户界面上发生的变化,例如在选中复选框时,就以不同的方式渲染用户界面。

3.3.3  阶段侦听器

有些时候,需要编写在请求处理生命周期中的确定时间上执行的代码。例如,可能想在继续到下一阶段之前,对视图组件树中的某件事检查两次。JavaServer Faces为做这个工作提供了简单途径:允许开发叫做阶段侦听器的定制Faces组件。简而言之,阶段侦听器提供了在生命周期不同阶段内不同点上执行定制代码的简单途径。例如,可能想根据运行时动态提供的值定制错误消息,或者想在处理回传生命周期之前验证这个会话已经建立了数据库连接。

构建阶段侦听器不过是编写实现PhaseListener接口的Java类而已。在类中,指定想让代码在哪个阶段执行,以及在这个时间点上要执行的实际代码。要把阶段侦听器添加到JSF应用程序,侦听器必须在faces-config.xml中注册,或者用编程方式注册到生命周期实例。

8和第11章提供了构建侦听器的示例。

16  需要记住的生命周期概念

本章迅速涉及了很多领域,但JSF开发人员不用担心要在开始构建JSF应用程序之前必须理解本章展示的每个项目。相反,新JSF开发人员可以在较高层次上体会JSF请求处理生命周期,理解生命周期为他们做了大部分通常会很烦琐的(表单处理)工作。高级JSF开发人员和有热情的JSF组件开发人员会发现,本章介绍的概念为他们转到更高级主题(包括定制组件开发)打下了坚实基础。

在总结本章之前,有一些关键的请求处理生命周期概念会贯穿本书的其余部分:

1)Faces请求处理生命周期替开发人员完成“繁重工作”。实际上,这就是实现它的原因——把烦琐的请求参数值处理工作从Web应用程序开发中拿出来,让开发人员把更多精力放在应用程序逻辑上。

2)JSF开发人员不必了解生命周期的每个细节就能构建简单的JSF应用程序。认为必须理解JSF生命周期全部细节,就像认为要理解发动机的工作方式才能驾驶汽车一样荒唐。对多数驾驶员来说,只要知道汽车偶尔需要加油就足够了。但是,对发动机的工作方式有点认识,在出现问题或者想对发动机做些修改以提高发动机性能时,会比较方便。

3)不过,对请求处理生命周期有坚实的理解,会为JSF高级开发提供良好的基础。当从构建JSF应用程序进一步走到构建定制JSF组件时,会发现对整个请求处理生命周期有完整的理解,会极有帮助,所以应当完整理解每个时刻在幕后发生的情况。