JSF 请求处理生命周期

2.2  请求处理生命周期

已经谈过JSF如何使用组件、事件、监听器和其他一些优雅的概念来简化Web开发。这也正是此节是关于处理请求的原因。为了使你理解框架是如何掩藏底层对Servlet API的处理,我们将分析Faces如何处理每一个请求。这将帮助你构建更好的应用,因为你知道确实发生了什么,并且知道会在什么时候发生。如果你是前端开发人员并且想要避免这些细节,你可以跳过此节。必要时随时回来参考这些内容。

在这一节将描述JSF如何处理Faces 自身产生的请求。换句话说,请求是由含有JSF 组件的页面产生的,响应也应当含有JSF组件(完全可以返回包含有JSF 组件的页面,即使初始请求并不是由JSF产生的;见第14章关于不同的请求处理情形的更多信息)。

图2-4是一个状态图,展示了JSF处理来自于客户端的请求时发生的事情——JSF请求处理生命周期。这一过程开始于JSF servlet 接收到一个请求(记住,JSF 构建于Servlet API之上)。表2-2总结了所有的处理阶段。共有6个主要的阶段,而事件则在它们中的大部分之后进行处理。

在大多数阶段后,JSF将事件广播到各种激活的监听器(事件可以与某特定的阶段相关联)。事件监听器执行应用逻辑或者操作组件;也可以直接跳到最后阶段,呈现响应阶段。监听器也可以跳到最后阶段并自己呈现响应。这在其需要返回二进制数据,执行重定向或者返回其他与JSF无关的内容,比如XML文档或普通HTML时更可能这样做。

有四个阶段会产生消息:应用请求值、处理验证、更新模型值和调用应用阶段。不管是否有消息产生,都在呈现响应阶段发送响应给用户,除非监听器、呈现器或者组件自身发送响应。

图2-4    请求处理生命周期。虚线的流是可选的。JSF在处理每个请求时,要经历多个阶段。每个阶段后都要调用事件监听器。监听器可以继续如常,报告错误并跳到呈现响应阶段,或者自己产生响应

整个过程背后的理想状况是到达调用应用阶段时,已存在一个完全组装好的组件树,所有的验证都已完成,而所有的后台bean或者模型对象全都进行了更新。简言之,JSF做了大量的工作:处理了有关请求的大量细节工作,并将它转换为由组件和事件构成的高阶视图。它甚至更新了相关组件的属性。

表2-2  JSF在处理到来的请求时经历多个阶段

阶    段


说    明


产生的事件

恢复视图

(restore view)


为选定的视图找到或者创建组件树。在此阶段,某些组件,如HtmlCommandButton,将产生动作事件(或者其他类型事件)


阶段事件

应用请求

(apply request value)


更新组件的值,使之等于请求中发送的值,可能需要使用转换器。如果出现错误将添加转换错误。也可以从请求参数中产生事件


阶段事件、数据模型事件、动作事件

处理验证

(process validation)


每一个组件进行自我验证(可以包含外部验证器)。要报告验证错误消息


阶段事件、数据模型事件、值改变事件

更新模型值

(update model value)


更新与组件相关的后台bean或者模型对象的值。要报告转换错误


阶段事件、数据模型事件

调用应用

(invoke application)


调用注册的动作监听器。默认的动作监听器也可执行由命令组件(如HtmlCommandButton)引用的动作方法并且选择下一个要显示视图


阶段事件、动作事件

呈现响应

(render response)


使用当前的显示技术(如JSP)显示选定的视图


阶段事件

为了更加清楚,我们使用Hello, world!示例来帮助解释生命周期。特别要分析检查产生第1章图1-8中输入的请求。实际的HTTP请求如代码清单2-1所示。

代码清单2-1  在Hello, world!应用显示图1-8之前,浏览器发送的HTTP 请求

这里不深入探讨HTTP 请求的细节,但是其中有几行与我们的讨论相关:

请求提交表单数据至相关URI /jia-hello-world/faces/hello.jsp。

Referer是指产生请求的页面。注意它与接收该请求的页面相同:/jia-hello-world/ faces/hello.jsp。

cookie被servlet容器用来将此请求映射到特定的会话。此例中,JSF用servlet 会话来存储当前视图(视图的状态也可以存储在客户维护的值中,比如隐藏字段)。

这是最重要的部分—这是JSF所处理的实际参数(&号用来分隔参数,而%3A则转义为一个冒号:)。第一个参数称为 welcomeForm:helloInput,值为64,它是输入到浏览器中的值。第二个参数称为 welcomeForm:redisplayCommand,值为Redisplay。最后一个参数称为welcomeForm,值为 welcomeForm。我们将在下一节看到如何处理这些参数。

一旦JSF接收到请求,它就创建和组装 javax.faces.context.FacesContext的一个实例。这个类表示请求的当前状态,请求具有到底层servlet请求对象之各个方面的钩子。这也是事件监听器获得当前视图的句柄、添加消息、记录事件等的地方。 JSF正是将这个对象作为请求处理生命周期的基础。我们将在以下几个小节中描述各个阶段。

2.2.1  阶段1:恢复视图

视图表示组成特定页面的所有组件。它被保存在客户端(通常存储在隐藏字段中)或服务器中(通常在会话中)。这个例子中,它被保存在服务器中,这是默认做法。每个视图由一棵组件树组成,并具有唯一的标识符。这个视图标识符和请求的额外路径信息相同。所以,对于被代码清单2-1中的所引用的路径信息/jia-hello-world/faces/hello.jsp来说,视图标识符是/hello.jsp——即servlet名称后面的内容。

因为这个请求是当用户点击hello.jsp中的按钮时产生的,因此发送此请求的页面和接收它的页面是相同的。页面提交给自己的过程称为是回送(postback)。如果已习惯使用Struts之类的框架 ——它们将应用代码分离到对应特定URL的Action类中,对此你可能会有些陌生。在这些框架中,URL通常很短,就像一个粗粒度的特定事件,告诉框架 “执行这个动作”。

JSF事件表示较细粒度的事件,诸如“用户执行此命令”或“用户改变此控件的值”。关于这些事件,最重要的是它们关联到被请求的最后一个页面。当JSF应用从现有的页面接收到请求时,它必须知道是哪个页面发出这个请求,以便它能够识别用户产生了哪个事件,并将事件与发送页面上的组件相关联。

这就是恢复视图阶段的主要工作——找出当前的视图并对其应用用户的输入。这时,它将从用户的会话中查找视图。如果服务器中的视图无效(用户之前没有访问过该页面),框架将舍弃当前视图(如果有的话),并且基于请求的视图标识符创建一个新视图。一旦视图被创建或者获得,它便被存储在当前的FacesContext中。

第1章的代码清单1-1列出了Hello, world!应用程序中的hello.jsp的代码。现在来看看表示此页面的组件树(见图2-5)。

图2-5  hello.jsp产生的组件树

你可以看到,页面被表达为一棵简单的组件树。视图开始于UIViewRoot组件,它是该页面上其他组件的容器。它有一个儿子,即标识符为welcomeForm的HtmlForm组件。此表单有多个儿子,包括称为helloInput的HtmlInputText组件和两个分别称为redisplayCommand和goodbyeCommand的 HtmlCommandButton组件。它还有一个子组件HtmlOutputLabel,该组件又有一个儿子HtmlOutputText组件。

恢复视图也确保了组件的值,与树中的组件相关联的事件监听器、验证器或者转换器,都被恢复。这里,HtmlInputText组件有个LongRange 验证器与之关联,它也随同组件在视图中被恢复。另外,redisplayCommand有个action 属性,而goodbyeCommand有个actionListener属性,它们都在视图中被恢复。

如果组件树中的组件都绑定到后台bean 属性,则bean的属性和组件的实例就要彼此保持同步。在这个例子中,HelloBean的controlPanel属性将与视图中名为 controlPanel的HtmlPanelGrid组件保持同步。这样允许后台bean监听器方法在处理事件时,在代码中可以操作组件。

视图语言也是在这个阶段设置的,它基于发送到浏览器的HTTP 请求中的值。如果这是个回送请求,JSF 将进行下一阶段的处理。然而,如果是初始请求(用户首次请求这个页面),JSF 将跳到呈现响应阶段,因为没有用户输入需要处理。

2.2.2  阶段2:应用请求值

每个接受输入的UI组件都有代表用户原始数据的被提交值(submitted value)。在应用请求值阶段,框架基于请求中的参数设置被提交值。这一过程称为解码。

在hello.jsp中,每个组件都指定了一个组件标识符,如:

这就用标识符helloInput声明了一个 HelloInputText组件。当JSF 将这个组件编码为HTML时,发送给浏览器的标识符就是,由该组件的标识符加上其父组件的标识符作为前缀组成的。发送给浏览器的标识符,通常是输入元素的 id属性,称为客户端标识符。例如,此HtmlInputText组件将有个客户端标识符为welcomeForm:helloInput,因为它是名为 welcomeForm的HtmlForm 的子组件(如图2-5所示)。

如我们在清单2-1所见的HTTP 请求,两个参数中有一个是helloInput,值为"64"。在这个阶段,JSF查询每个组件并对其进行解码。组件(或其呈现器)首先通过查找与其客户端标识符名称相同的参数来完成这个任务。一旦找到参数,组件的值便设置为参数的值。所以,这时,HtmlInputText 组件将设置其被提交值为"64"。

每个具有可编辑值的输入控件都有个 immediate属性。如果这个属性为true,验证将在本阶段进行而不是要等到处理验证阶段。验证的处理过程是一样的,见下一节的详细描述(在这个例子中,没有控件的immediate属性设置为true)。动作源,比如按钮或者超链接,也有immediate属性,如果该属性被设置为true,它将在本阶段触发动作事件。早期处理这些事件是很方便的,例如,你可能会有个可以忽略表单中所有值的取消按钮,或者有个超链接仅接受特定控件的值(这时,这些控件的immediate属性就该设置为true)。

在请求中发送的另一个参数是"welcomeForm",值为"welcomeForm"。这个参数存在时,HtmlForm 组件都设置其submitted属性为true。这允许你根据用户是否提交表单执行不同的逻辑(一个视图可有多个表单)。

在这一阶段,解码代码也可添加事件或者基于请求执行其他操作。在这个示例应用中,在此阶段,其他参数,如"welcomeForm:redisplayCommand",被匹配客户端标识符的 HtmlCommandButton控件转换为动作事件。一旦动作事件被创建,它便被添加到FacesContext中,以便随后在调用应用阶段做进一步处理。

这个阶段执行后,所有已有的事件都会广播给激活的事件监听器。任何呈现器、组件或者监听器都可以截断生命周期,直接跳到呈现响应阶段,或者终止处理(如果它们自己呈现整个响应)。否则,每个输入控件都将自行解码,并最终具有基于当前请求的最新值。这正如此例所为。

2.2.3  阶段3:处理验证

在处理验证阶段,JSF遍历组件树并检查每个组件,看是否每个组件的被提交值都可以接受。因为每个输入组件的被提交值都在应用请求值阶段进行了更新,这时组件已经有了来自用户的最新数据。验证发生前,被提交值将首先由注册到该组件的转换器或者默认转换器进行转换。然后验证直接由组件进行或者委托给一个或者多个其他验证器来进行。

如果转换和验证对提交了值的所有组件都成功,将到生命周期的下一阶段。否则,控制将跳到呈现响应阶段,随验证和转换错误消息而完成。

Hello, world! 有一个LongRange验证器关联到HtmlInputText组件,而且组件的required属性设置为true:

这样,视图被创建时,验证器实例也被创建,并且关联到HtmlInputText组件。

因为UI 组件的required 属性设置为true,它将首先检查该组件的被提交值是否为空。这里,值为"64",所以无疑是非空的。接下来,被提交值转换成 helloBean.numControls属性的类型,这是一个整数。然后调用LongRange验证器来检查其父组件的值是否有效。在这里,有效意味着值介于1~500(包含)之间。因为64 确实是在1~500之间,所以该组件被视为有效。

一旦完成对组件的被提交值的验证,其本地值便会根据转换过的提交值进行设置,这里就是整数64。如果本地值被改变,组件也产生值改变事件。

在这里,值改变事件(以及其他任何与此阶段相关的事件)被触发并且被相关的监听器所处理。这些监听器可以直接输出响应或者跳到呈现响应阶段。

如果所有的被提交值都视为有效,JSF将进入生命周期的下一阶段的。

2.2.4  阶段4:更新模型值

现在我们已经确保组件的所有本地值都已被更新且是有效的,并具有正确的类型,这就可以开始处理相关的后台bean 或模型对象了。因为对象通过JSF EL表达式与组件相关联,所以要计算表达式的值,并且根据组件的本地值更新属性。再次来看看HtmlInputText的声明:

可以看到组件的value属性是表达式"# {helloBean.numControls}"。JSF将使用该表达式查找保存在关键字helloBean下的实例,它将依次查找每个上下文范围—— 从请求、会话、到应用(见2.4.1节关于不同范围的变量的详细信息)。这里,bean被存储在会话中。一旦它被找到,组件将设置其特定的属性值(这里是 numControls)为组件的本地值,当前是整数值64。

一旦完成此阶段,框架将向激活的监听器广播所有事件。并且,监听器总是可以跳到呈现响应阶段或者自己产生返回响应。

最重要的是要记住,我们已经基于用户的输入更新了组件的值,验证了这些值,并更新了相关的bean而没有编写任何应用代码。这就是JSF的真正威力——它会自动完成了大量的UI 处理任务,因为除了编写讨厌重复的代码外,还有很多东西要做。

2.2.5  阶段5:调用应用

现在必需的后台bean和模型对象都已更新,就可以开始涉及具体业务了。在调用应用阶段,JSF 向所有激活的监听器广播此阶段的所有事件。前面的例子中,针对redisplayCommand HtmlCommandButton控件产生了动作事件。所以在此阶段,JSF 将发送动作事件到所有注册到该组件的动作事件监听器。这里有两个:在恢复视图阶段恢复的动作监听器和默认的动作监听器(自动为每个组件注册的默认监听器)。

下面是redisplayCommand的声明:

组件的actionListener 属性是JSF EL 表达式"#{helloBean.addControls}"。在此阶段,JSF将求解表达式并且执行保存在会话中的helloBean实例的addControls方法。下面就是该方法的代码:

这段代码创建了numControls实例并将其添加到controlPanel,而后者是页面中的HtmlPanelGrid实例(其儿子必须首先被清空,否则每次此方法执行后其儿子的列表将不断增长)。回想在恢复视图阶段,视图和HelloBean的controlPanel属性之间的绑定便已建立。而且,numControls 属性在更新模型值阶段也被更新了。这个动作监听器方法执行后,JSF 将调用默认的动作监听器。该监听器将控制委托给,注册到产生该事件的组件中的其他动作方法,然后基于动作方法的逻辑结果选择下个页面。在这里,为 goodbyeCommand 注册了动作方法,而为redisplayCommand则没有,而它正是产生该事件的组件。所以默认的动作监听器将只是简单地显示当前视图。

如在2.1.6节中讨论的,动作监听器或动作方法可以执行大量的操作,比如自己呈现响应、添加应用消息、产生事件或者执行应用逻辑。它们也可以直接跳到呈现响应阶段以避免任何额外的应用事件处理。动作方法与导航系统集成,所以它们可以决定下一个要装入的页面。

需要指出的是,即使所有这些处理都在发生,这里也是应用代码的生长之处——通常你根本不需要担心请求本身。一旦所有的监听器都执行,JSF便可以向用户显示响应了。

2.2.6  阶段6:呈现响应

到此,所有由框架进行的处理和应用逻辑都已经发生。剩下的就是发送响应给用户,这也是呈现响应阶段的主要目标。本阶段的第二个目标是保存视图的状态,以便用户再次请求时可以在恢复视图阶段恢复该视图。视图要在本阶段保存,是因为通常视图保存在客户端,所以它也是发送给用户的响应的一部分。此时,JSF在服务器保存视图,所以视图最有可能保存在用户的会话之中。

记住,JSF并不与特定的显示技术相关。因此,我们有多种方法用来呈现响应:

l    仅使用视图中的控件的编码方法的输出;

l    将编码方法的输出与产生标记的应用代码集成;

l    将编码方法的输出与保存在静态模板中的标记集成;

l    将编码方法的输出与动态源,比如JSP,集成。

所有的JSF都要求实现最后一种方法,可以将其浓缩为,将请求转发到视图标识符所表示的源。在例子中,标识符是"/hello.jsp",所以调用的页面将仅是重新显示。JSF 实现可以自由实现其他方式,以便它们可以与其他显示技术相集成。见附录A,那里描述了不用JSP时,如何在其他技术中使用JSF 的例子。

在每个组件的编码阶段,都要调用转换器以将组件的值转换成供显示的字符串。所以在此例中,HtmlInputText组件的整数值64将转换成字符串。

至此结束。呈现响应阶段是JSF 请求处理生命周期的最后阶段。一旦它结束了,Web容器将结果字节通过物理传输发回给用户,然后在用户的浏览器中显示。本例的输出,即在浏览器中显示的结果,如图1-8所示。

现在,你已经能完全理解JSF 是如何工作的,接下来,我们来讨论开发JSF应用的另一个重要方面。

 

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页

打赏作者

dontsan

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值