JSF是一个Web应用,也会遵循请求/相应的架构,与JSP的生命周期完全相似:客户端发送一个HTTP请求,服务器端对请求进行处理之后,向客户端送回HTTP响应,整个JSP生命周期完成。
JSF将请求/响应的生命周期细化为更多阶段,从而支持更加复杂的UI组件模型。
1、JSF将用户请求分成两种: 1、 初始请求:当用户直接向某个页面发送请求时,请求没有附加任何的参数信息。2、 提交表单:单击表单的提交按钮、提交超链接时,就会发生提交表单请求。 2、JSF的6个生命周期: 2.1、恢复视图阶段:
当客户端向某个JSF页面发送请求时,例如单击了某个超链接或者按钮时,JSF就开始恢复视图阶段。
在这个阶段,JSF将会为该页面创建对应的视图,并将事件监听器、输入校验器等连接到页面所包含的UI组件上,使用FacesContext实例来保存视图对象。此时,应用相关的所有组件,包括UI,事件处理器,转换器和输入校验器都可以访问到该FacesContext实例。
如果是初始请求:JSF将在该阶段创建一个新的视图对象,并将生命周期阶段直接推进到生产响应阶段。
如果是提交表单:则对应该页面的视图已经存在,JSF将会采用客户端或服务器端的信息来恢复视图。
2.2、应用请求值阶段:当视图恢复完成后,每个组件都会调用它的decode方法从请求参数中提取新的参数值,并保存在本地组件上(保存之前需要先进行类型转换,如果值转换失败,JSF将会使用FacesContext来保存与组件相关的错误消息,并将消息放入消息对了中,这些消息将会等到输出响应阶段几种处理)。
如果任何组件的decode方法或者事件监听器调用了FacesContext的renderResponse方法,那么JSF将会直接推进到生成响应阶段。
如果页面的某个组件设置了immediate="true",这个阶段还会处理这些组件相关的验证、转换和事件等。
在这个阶段,应用可以重定向到其他资源,或者生成一个不包含Java Server Faces组件的响应,如果需要实现这种处理,开发者必须调用Facescontext。responseComplete来跳过生成响应阶段。
这个阶段结束后,所有组件都被设置成了他们的新值,所有消息和事件都被放入队列。
2.3、处理输入校验阶段:处理UI组件注册的输入校验器,JSF会用各UI组件上的本地值和对应输入校验规则进行比较,如果本地值无效,JSF就会把对应的错误消息添加到FacesContext实例中,而JSF的生命周期也将直接推进到生成响应阶段,显示页面可以通过或者来显示输入校验的错误信息。
在这个阶段中,任何validator方法和事件监听器调用了当前的FacesContext实例的renderResponse方法,那么应用的生命周期将会直接推进到生成响应阶段。
如果需要在该阶段重定向到其他资源,或者生成一个不包含Java Server Faces组件的响应,开发者可以调用FacesContext.responseComplete来跳转到生成响应阶段。
2.4、更新模型的值阶段:通过了输入校验之后,使用UI组件的本地值更新与之绑定的托管Bean(将用户输入的数据传送给服务器端的托管Bean),JSF只会更新绑定到输入组件的托管Bean。
如果无法更新,则会生成错误信息,生命周期直接推进到生成响应阶段,可以采用或来显示错误信息。
在这个阶段中,如果任何updateModels方法或事件监听器调用了当前FacesContext实例的renderResponse方法,生命周期将会直接推进到生成响应阶段。
如果需要在该阶段重定向到其他资源,或者生成一个不包含Java Server Faces组件的响应,开发者可以调用FacesContext.responseComplete来跳转到生成响应阶段。
2.5、调用应用阶段:在这个阶段中,JSF将会处理应用级别的事件,例如提交表单或链接到其他页面。
如果需要在该阶段重定向到其他资源,或者生成一个不包含Java Server Faces组件的响应,开发者可以调用FacesContext.responseComplete来跳转到生成响应阶段。
2.6、生成响应阶段:在这个阶段中,应用准备想客户度输出响应,如果使用了JSP页面,JSF将调用JSP容器来处理响应。如果是初始请求,页面上的组件将被JSP容器添加到组件树中;如果不是初始请求,那么所有组件都已被添加到组件树中,因此不需要再次添加。
如果在提交表单阶段,并且在应用请求值阶段、处理阶段或更新模型阶段遇到了错误,应用将会选择生成最初的页面。
生成响应后,应用的响应状态也被保存下来。
JSF提供了PhaseId类来代表生命周期阶段,本质上是一个枚举类,常常使用的常量:
ANY_PHASE:任意一个生命周期阶段RESTORE_VIEW:恢复视图阶段
APPLY_REQUEST_VALUES:应用请求值阶段
PROCESS_VALIDATIONS:处理输入校验阶段
UPDATE_MODEL_VALUES:更新模型的值阶段
INVOKE_APPLICATION:调用应用阶段
RENDER_RESPONSE:生成响应阶段
来自:http://blog.sina.com.cn/s/blog_a1c8858c0101a3fw.html
JSF请求处理生命周期的高度概述)
(2012-11-30 09:52:35)
标签: 杂谈 |
前一章演示的简单JavaServer Faces应用程序示例,介绍了JSF的许多实用方面。但是,在进入更高级JSF开发时,有一个JSF的关键部分非常重要:请求处理生命周期。请求处理生命周期担当着使JavaServer Faces运行起来的“幕后”引擎的角色。本章研究JavaServer Faces请求处理生命周期背后的关键概念,并解释如何用定义良好的基于事件的方式处理Web请求。对请求处理生命周期的全面理解非常重要,因为在后续章节介绍高级JSF开发主题时,会多次提到请求处理生命周期的不同阶段。
3.1
从历史上看,Web应用程序必需的大部分开发,主要是处理Web客户端的HTTP请求。随着Web从传统的静态文档传送模型(在这种模型中,只请求静态Web页面,没有参数)转变到动态环境(要求Web应用程序处理大量进入参数),对日益复杂的请求的处理需求,不可避免地增长起来。这使Web应用程序开发变得相当烦琐。例如,请看下面在Java servlet或JSP小程序中处理进入请求参数firstname和lastname的代码:
String firstname = request.getParameter("firstname");
String lastname = request.getParameter("lastname");
// Do something with firstname and lastname
考虑到现在多数高级Web应用程序即使不处理几千个,也要处理上百个参数,就可以看出上面这种参数处理方法很容易就会变得很烦琐。
有经验的Web开发人员都知道,编写处理进入请求参数的代码通常包含以下步骤:
幸运的是,请求处理生命周期可以用一种连贯的、基于事件的方式处理这项工作。
专家组意见 |
3.1.1
简而言之,过去必须自行编写代码才能处理的必要的后端处理,现在全由请求处理生命周期执行。除了处理进入的请求参数,它还管理服务器端的用户界面组件集,并把它们与用户在客户端浏览器中看到的组件同步。
3.1.2
对比其他更传统的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.3
如图3-1所示,JSF请求处理生命周期能把服务器端Java Bean属性自动同步到有层次的组件集(根据呈现给客户端用户的用户界面)的能力,是它与其他Web技术相比的主要优势。
由于Web天生是无状态的,即客户端与服务器之间的一个事务对前一个事务没有记忆,所以JavaServer Faces通过自动维护代表客户端当前状态的服务器端视图(view)而解决了这个问题。这允许JSF开发人员把精力集中在服务器端组件,由请求处理生命周期或“衔接”(plumbing)负责服务器端视图的同步和在客户端浏览器上显示什么。编写代码处理每个请求值或者修改用户界面状态的烦琐工作,都通过一组阶段(phase)(每个阶段中,都用连贯的方式执行具体的数据处理任务)由JavaServer Faces请求处理生命周期自动处理
图3-1
3.1.4
处理进入的请求数据,通常需要不同类型的工作,包括检查进入的数据是否有效、触发服务器端的应用程序逻辑以完成请求,以及最后把响应渲染给客户端。JSF请求处理生命周期用前后连贯的顺序执行这些任务,而且在一组定义良好的阶段的控制之下。这种方式允许每个阶段清晰地描述执行本阶段之前需要存在的前提条件,以及本阶段执行之后会存在的后置条件。
下面是生命周期的各个阶段。
图3-2显示了这些阶段合在一起构成的请求处理生命周期的高级视图。可以看到,它执行了Web应用程序中的对进入数据进行处理的全部任务。贯穿本章和本书的其余部分,将介绍这个图中的不同事件和阶段。
现在来深入研究每个生命周期处理阶段中到底发生了什么。
图3-2
1.恢复视图
前面提到过,Faces视图是用户界面组件的服务器端树,它提供了在客户端显示的用户界面的镜像表示(请参见图3-3)。恢复视图阶段的任务是根据前一个事务恢复现有视图,或者根据新请求创建新视图。如果是新请求(“没有回传”),就创建新视图,并保存在父容器对象FacesContext内。FacesContext充当与当前请求有关的数据在通过整个请求处理生命周期过程中的存储。Web开发人员不必担心多个用户请求会把FacesContext中的应用程序数据混淆,因为servlet API保证请求操作是线程安全的,即所有对FacesContext的操作,都保证是在一个独立线程上针对每个用户请求进行的。
图3-3
2.应用请求值
恢复了视图之后的下一阶段——应用请求值阶段——做的是对进入的请求值或信息名称-值对进行处理。视图层次结构中的每个用户界面组件节点,现在都能得到客户端发送来的更新值,如图3-4所示。
图3-4
在幕后,JSF运行时在用户界面组件树的视图(或UIViewRoot)上调用高级方法(processDecodes(
应当指出,只有能够容纳值的用户界面组件(例如输入字段)才有新值应用到它们。一般来说,有两类组件:一类是有值的组件,例如文本字段、复选框和标签;另一类是引起动作的组件,例如按钮和链接。所有具有value属性的组件都实现ValueHolder接口。所有表单元素类型的组件,如果它的值可以由用户编辑,那么就都实现EditableValueHolder接口。所有引起动作的组件(按钮或链接)都实现ActionSource接口。
JSF1.2提示 |
例如,按钮(UICommand或其他实现ActionSource的组件)在表单提交期间,不用新值更新;它只需记录自己是否被单击。如果单击了,就引起称为动作事件(ActionEvent)的事件进入队列。稍后就会看到动作事件到底是什么,以及它如何允许与按钮或链接单击相对应的定制代码的执行。
虽然请求处理生命周期用连贯的方式处理不同阶段,但阶段的执行顺序可以为特殊情况而变化。例如,可能想向表单添加一个Cancel按钮。在单击时,它会跳过所有验证,不处理表单的值,直接导航到另一个页面。要改变请求处理生命周期的处理顺序,只需设置组件的immediate属性。本章后面将会介绍,在不同的组件上设置immediate属性会有不同的效果。
3.处理验证
应用请求值阶段完成之后,就要执行对进入数据进行转换和验证的处理验证阶段(第7章将详细解释数据类型转换和验证)。JSF运行时调用主processValidators(
注意 |
在第2章的JSFReg示例中看到过,验证器和转换器可以用若干种方式与用户界面组件关联。在示例中,验证请求可以通过设置组件本身的属性(例如把inputText组件的required属性设置为“true”)与组件关联,也可以通过注册定制验证代码(例如通过设置组件的validator属性,向组件附加电子邮件验证方法)与组件关联。第2章的示例也有一个转换器,通过插入convertDateTime转换器标签作为输入组件的子组件,与“出生日期”(dob)inputText(UIInput)组件关联。
验证(或转换)失败的组件的valid属性会设置成“false”,并把FacesMessage消息放进FacesContext队列,如图3-5所示。然后,当响应被渲染回用户(在渲染响应阶段)时,可以用Faces的Message或Messages组件显示消息,以便用户纠正错误或重新提交。
图3-5
4.更新模型值
假设进入的数据已经通过验证和转换,现在要把数据分配给值绑定到用户界面组件的模型对象。回忆一下第2章的示例。在这个示例中,我们创建了一个Java类UserBean,把它注册成托管bean,并用JSF表达式语言把属性绑定到页面上的不同用户界面组件。正是在更新模型值阶段,实际的托管bean或模型对象的属性,被它们绑定的用户界面组件的新值更新。
这个操作背后实际的机制与其他阶段相似:在UIViewRoot实例上调用processUpdates(
图3-6
5.调用应用程序
现在,已经看到了JSF请求处理生命周期如何执行从Web请求获得进入数据、验证数据/把数据转换成合适的服务器端数据类型,以及把数据分配给模型对象等工作。对Web开发人员来说,这只是Web应用程序编写工作的一半。另一半是:取得进入数据,真正对数据进行操作,例如调用处理数据的外部方法。这正是调用应用程序阶段的作用。
回忆本章前面所述,用户界面组件既可以容纳值(实现EditableValueHolder)也可以是ActionEvent来源(实现ActionSource)(例如在单击按钮(UICommand)时)。定制动作代码,又称为动作方法或动作侦听器方法就是在调用应用程序阶段调用的。
幕后,在调用应用程序阶段中,UIViewRoot的processApplication(
图3-7
第8章将重新研究JSF事件模型到底如何工作,并提供关于Faces事件处理确切顺序的更多细节。
还应当指出,导航到不同页面,也发生在调用应用程序阶段。第5章将介绍一个基本的登录应用程序,研究导航到底是怎么发生的。登录应用程序使用了一个绑定到login(UICommand)按钮的简单动作。当用户单击按钮时,触发动作事件,动作事件又在调用应用程序阶段调用定制动作方法,处理登录凭据。请记住,这个代码只有在进入的数据已经通过前面阶段(转换和验证已经完成)之后才会执行。登录成功时,会导航到新页面。
6.渲染响应
现在到了JSF请求处理生命周期的最后阶段,在这个阶段渲染响应。为把整个响应渲染回客户端,又一次逐级对每个组件调用encodeXX(
除了把响应渲染给客户端,渲染响应阶段还保存视图的当前状态,以便可以在后续Web请求中访问和恢复状态。图3-8演示了如何用客户端标记渲染响应。这时,视图的当前状态被保存下来,供未来请求之用。
另外,实际还有更多精细的幕后细节与渲染响应阶段有关,但本章介绍的范围之内。其中包括:处理静态内容(又称为“模板”源)与组件的动态内容交织的情况;处理各种动态输出源;在保持顺序正确的同时把它们协调成一个可视的响应。一般来说,使用JSF时不需要处理这些细节。
图3-8
JSF1.2提示 |
3.2
看过每个请求处理生命周期阶段背后的理论之后,是时候看看生命周期的实际作用了。回顾第2章所示的JSF注册(JSFReg)应用程序(如图3-9所示)。我们要作为与应用程序交互并提交注册表单(register.jsp)的用户,经历全部生命周期阶段。
(1)查看register.jsp页面的初始请求:为了第一时间看到注册表单页面,用户需要直接发送对注册页面URL的请求,用精简方式触发请求处理生命周期的一次运行。请求首先由Faces控制器servlet处理,控制器为这个请求创建一个FacesContext实例,并初始化对生命周期的调用。由于这是查看注册页面的初始请求(也叫做“无回传”请求),恢复视图阶段创建一个空视图(UIViewRoot)组件树,并把它保存在FacesContext实例中。
图3-9
视图创建之后,因为在请求中没有进入字段数据需要验证或处理(也叫做“无回传”请求),生命周期立即前进到渲染响应阶段。在这个阶段,空视图组件树由注册页面源代码中引用的组件(显示输入字段和提交按钮)填充。树填充之后,组件就把自己渲染到客户端。同时,保存视图组件树的状态,供后续请求使用。用户现在看到在浏览器中渲染的注册页面。
(2)用户在注册页面中输入无效数据:假设用户忘记输入了自己的姓,还输入了格式错误的日期,就单击了Register按钮(如图3-10所示)。
图3-10
当JSF运行时接收到请求时,进入初始的恢复/创建视图阶段,这次它恢复刚才在用户前一个请求之后保存的视图组件树。这通常叫做“回传”,因为这个请求的HTTP方法是POST(因为要“传送”新表单数据)。然后进入应用请求值阶段,即使请求中的进入值不完全有效,也用请求中的进入值更新组件。在这个阶段没有错误发生,因为每个用户界面组件只是把请求参数的提交值保存为String值,还不是转换后的(服务器端数据类型)值或验证过的值。用户界面组件把这个值保存在一个特殊的经过预转换/预验证的submittedValue JavaBean属性中,它只是原样保存请求参数的String值。
应用请求值阶段完成后,启动处理验证阶段。这时,由于进入的日期数据格式无效,无法转变成java.util.Date数据类型(对应着托管bean UserBean的“dob”属性),发生转换错误。一条消息被放入队列,组件被标记为无效,处理继续进行。余下的有效字段值被应用到对应的用户界面组件。
在每个用户界面组件调用它的验证方法时,要容纳lastName值的组件遇到验证错误,因为在回传请求中没有为它提供值。还记得前面姓氏输入字段的“required”属性设置为“true”。作为验证错误的结果,生命周期把UIInput组件lname(last name)的属性设置为无效,并把对应的表示这个字段必须有值的Faces消息放入队列。这时,处理验证阶段完成。
因为有验证和转换错误,所以生命周期直接跳到渲染响应阶段,然后渲染同一个注册页面(register.jsp),把对应的错误消息放在last name字段和日期字段旁边。回想一下,每个消息组件到每个输入字段组件的分配,是通过分配它们的ID实现的,如下所示:
除了把响应渲染给客户端,渲染响应阶段也保存视图组件树,供后续请求使用。
(3)用户纠正验证错误,重新提交表单:在响应中看到错误消息后,用户对表单进行纠正,提供姓氏并输入格式正确的日期值,然后重新提交。这次,在处理请求时,恢复视图阶段恢复保存的视图树,并前进到应用请求值阶段,在这个阶段把新值应用到视图组件树。阶段转移到处理验证阶段,这次在这个阶段没遇到验证或转换错误。然后触发更新模型值阶段。这时,托管bean(UserBean)的属性被表单中提交的新值更新,如图3-11所示。
图3-11
更新模型值阶段完成之后,触发的下一阶段是调用应用程序阶段。还记得调用应用程序阶段为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中定义的规则确定是否需要导航。
注意 |
图3-12
3.3
看过JSF请求处理生命周期在默认情况下对基本表单的处理方式,现在来看些稍微复杂的示例。
3.3.1
这一节介绍Faces的一项极为重要的特性:immediate属性,它允许更灵活的生命周期执行方式。
立即处理动作事件
假设想在注册页面上添加一个Cancel按钮,单击这个按钮时,不论表单上输入了什么,都会立即把用户路由回main.jsp页面。要实现这个功能,必须添加一个返回主页面的导航规则,还要在页面上添加一个按钮或链接组件,组件的动作设置为导航规则“cancel”的from-outcome。下面是faces-config.xml中的新导航规则:
下面是新添加到Register页面的Cancel按钮,它的action硬编码设置成“cancel”:
但是,如果就此停止,那么很快就会遇到问题!如图3-13所示,如果想再次运行应用程序,但是立即单击Cancel按钮,就会卡在那里。出现这个问题是因为即使刚才用空表单单击Cancel按钮,仍然把它当作“回传”请求进行处理,这意味着JSF生命周期开始进行处理,而且假定从表单传来了进入的名称-值对。但是由于表单实际是空的,所以在导航处理程序能处理“cancel”动作事件并把同一页面响应渲染回客户端之前,先遇到了验证错误。显然,需要有一个在正常生命周期处理流程之中允许异常的解决方案。JSF在它的immediate属性中提供的就是这个功能。
图3-13
如果给Cancel按钮(或任何UICommand组件)添加了immediate属性,并把它的值设置成“true”,就能允许生命周期立即跳过验证,导航回main.jsp页面。一般来说,在UICommand组件上把immediate属性设置为“true”,会引起动作事件在处理验证阶段之前,在应用请求值阶段就立即触发,这样就不会遇到验证错误。这样就有了把生命周期“短路”的效果,可以避免验证,取消更新任何模型的值。
3.3.2
实现EditableValueHolder接口的组件(像输入字段)带有选项,可以根据immediate属性提供的值,让它们的验证和转换在应用请求值阶段或通常的处理验证阶段立即执行。如果值是“true”,那么该组件的验证和转换立即发生。更重要的是,把immediate属性设为“true”,允许在进入处理验证阶段前用新验证的值更新组件,余下的没有immediate的组件的验证会在处理验证阶段发生。这通常在执行只针对用户界面的修改时有用,例如在不提交和验证整个表单数据的情况下就启用输入字段,使字段变成可编辑。
在第8章,会发现如何用immediate属性和值修改侦听器在不验证整个表单内容的情况下,修改用户界面属性。
专家组意见 |
3.3.3
有些时候,需要编写在请求处理生命周期中的确定时间上执行的代码。例如,可能想在继续到下一阶段之前,对视图组件树中的某件事检查两次。JavaServer Faces为做这个工作提供了简单途径:允许开发叫做阶段侦听器的定制Faces组件。简而言之,阶段侦听器提供了在生命周期不同阶段内不同点上执行定制代码的简单途径。例如,可能想根据运行时动态提供的值定制错误消息,或者想在处理回传生命周期之前验证这个会话已经建立了数据库连接。
构建阶段侦听器不过是编写实现PhaseListener接口的Java类而已。在类中,指定想让代码在哪个阶段执行,以及在这个时间点上要执行的实际代码。要把阶段侦听器添加到JSF应用程序,侦听器必须在faces-config.xml中注册,或者用编程方式注册到生命周期实例。
第8和第11章提供了构建侦听器的示例。
3.4
本章迅速涉及了很多领域,但JSF开发人员不用担心要在开始构建JSF应用程序之前必须理解本章展示的每个项目。相反,新JSF开发人员可以在较高层次上体会JSF请求处理生命周期,理解生命周期为他们做了大部分通常会很烦琐的(表单处理)工作。高级JSF开发人员和有热情的JSF组件开发人员会发现,本章介绍的概念为他们转到更高级主题(包括定制组件开发)打下了坚实基础。
在总结本章之前,有一些关键的请求处理生命周期概念会贯穿本书的其余部分:
(1)Faces请求处理生命周期替开发人员完成“繁重工作”。实际上,这就是实现它的原因——把烦琐的请求参数值处理工作从Web应用程序开发中拿出来,让开发人员把更多精力放在应用程序逻辑上。
(2)新JSF开发人员不必了解生命周期的每个细节就能构建简单的JSF应用程序。认为必须理解JSF生命周期全部细节,就像认为要理解发动机的工作方式才能驾驶汽车一样荒唐。对多数驾驶员来说,只要知道汽车偶尔需要加油就足够了。但是,对发动机的工作方式有点认识,在出现问题或者想对发动机做些修改以提高发动机性能时,会比较方便。
(3)不过,对请求处理生命周期有坚实的理解,会为JSF高级开发提供良好的基础。当从构建JSF应用程序进一步走到构建定制JSF组件时,会发现对整个请求处理生命周期有完整的理解,会极有帮助,所以应当完整理解每个时刻在幕后发生的情况。
来自:http://blog.sina.com.cn/s/blog_a1c8858c0101a3ge.html
基友吐血推荐的博文,对JSF解释的挺好的。