2.8 JSF的运行流程和生命周期
从本质上来看,JSF应用依然是一个Web应用,因此也会遵循请求-响应的架构,因此与JSP页面的生命周期完全相似:客户端浏览器发出一个HTTP请求,服务器端对请求进行处理之后,向客户端送回HTTP响应,整个JSP生命周期完成。
JSF的生命周期与JSP生命周期的不同之处在于:JSF将请求-响应的生命周期细化为更多阶段,从而支持更加复杂的UI组件模型,从而允许按一种更加有序的方式对组件数据进行转换和验证;对UI组件事件进行处理,最后将UI组件的数据传递到托管Bean。
JSF应用与JSP应用(或者说传统MVC框架)的显著区别在于:JSF应用模拟了传统C/S编程模型,它通过一种机制保证多个页面之间的状态连续。因此JSF生命周期中的第一个阶段是:JSF实现会根据应用状态来创建视图。当客户端提交请求时,JSF实现会按顺序依次经过多个阶段,每个阶段分别执行不同的任务,JSF应用中的不同阶段就被称为JSF的生命周期。
图2.31显示了JSF应用的6个生命周期。
图2.31 JSF应用的6个生命周期
从图2.31可以看出,JSF应用也可以直接从恢复视图阶段跳转到生成响应阶段,JSF将用户请求分成两种:
初始请求(initial request):当用户直接向某个页面发送请求时,该请求没有任何请求参数,这种请求就属于初始请求。
提交表单(postback):当用户浏览某个包含表单的页面,并单击该表单内的提交按钮、提交超链接时,就会发送提交表单请求了。
如果用户请求是初始请求,由于这种请求不包含请求参数,因此JSF应用无须复杂的处理,只需经历恢复视图、生成响应两个阶段。
除此之外,有时候应用可能需要重定向到其他资源,或者生成一个不包含Java Server Faces组件的响应,在这种情况下,开发者必须调用FacesContext.responseComplete来跳过生成响应阶段。
接下来详细介绍JSF应用的各个生命周期阶段。
2.8.1 恢复视图阶段
当客户端向某个JSF页面发送请求时,例如,用户单击了某个超链接或按钮时,JSF就开始了恢复视图阶段。在这个阶段中,JSF将会为该页面创建对应的视图,并将事件监听器、输入校验器等连接到页面所包含的UI组件上,并使用FacesContext实例来保存视图对象,而应用相关的所有组件,包括UI组件、事件处理器、转换器、输入校验器等都可访问到该FacesContext实例。
如果用户请求是一个初始请求,JSF将在该阶段创建一个新的视图对象,并将生命周期阶段直接推进到生成响应阶段。
如果用户以提交表单的方法来发送请求,则对应该页面的视图已经存在了,因此JSF将会采用客户端或服务器端(取决于JSF的配置)的信息来恢复视图。
2.8.2 应用请求值阶段
当视图恢复完成之后,页面中每个组件都会调用它的decode方法从请求参数中提取新的参数值,这些参数值将会保存到本地组件上——保存之前需要先进行类型转换,如果值转换失败,JSF将会使用FacesContext来保存与组件相关的错误消息,并将消息放入消息队列中,这些消息将会等到输出响应阶段集中处理。
如果任何组件的decode方法或事件监听器中调用了FacesContext的renderResponse方法,那么JSF将会直接推进到生成响应阶段。
如果页面上的某个组件设置了immediate="true",该阶段还会处理与这些组件相关联的验证、转换和事件等。
在这个阶段,应用可以重定向到其他资源,或者生成一个不包含Java Server Faces组件的响应,如果需要实现这种处理,开发者必须调用FacesContext.responseComplete来跳过生成响应阶段。
当这个阶段结束时,所有组件都被设置成它们的新值,而且所有消息和事件都被放入队列。
2.8.3 处理输入校验阶段
这个阶段主要是处理各UI组件注册的输入校验器,JSF会用各UI组件上的本地值和对应输入校验规则进行比较,如果本地值无效,JSF就会把对应的错误消息添加到FacesContext实例中,而JSF的生命周期也将直接推进到生成响应阶段,显示页面可以通过<h:message…/>或<h:messages…/>来显示输入校验的错误消息;不仅如此,应用请求值阶段的转换错误也会被显示出来。
在这个阶段中,如果任何validator方法或事件监听器调用了当前FacesContext实例的renderResponse方法,那么应用的生命周期将会直接推进到生成响应阶段。
除此之外,如果应用需要在该阶段重定向到其他资源,或者生成一个不包含Java Server Faces组件的响应,开发者可以调用FacesContext.responseComplete来跳过生成响应阶段。
2.8.4 更新模型的值阶段
当用户输入的参数通过了输入校验之后,JSF可以用UI组件的本地值来更新与之绑定(通过value或binding属性绑定)的托管Bean,这样就可将用户输入的数据传给服务器端的托管Bean——需要指出的是,JSF只会更新绑定到输入组件(UIInput组件)的托管Bean。
如果UI组件的本地值不能更新到与之绑定的托管Bean,应用的生命周期将会直接推进到生成响应阶段,应用同样会采用<h:message…/>或<h:messages…/>来显示错误消息,就像显示输入验证的错误消息一样。
在这个阶段中,如果任何updateModels方法或事件监听器调用了当前FacesContext实例的renderResponse方法,那么应用的生命周期将会直接推进到生成响应阶段。
除此之外,如果应用需要在该阶段重定向到其他资源,或者生成一个不包含Java Server Faces组件的响应,开发者可以调用FacesContext.responseComplete来跳过生成响应阶段。
2.8.5 调用应用阶段
在该阶段,JSF将会处理应用级别的事件,例如,提交表单或链接到其他页面。
如果应用需要在该阶段重定向到其他资源,或者生成一个不包含Java Server Faces组件的响应,开发者可以调用FacesContext.responseComplete来跳过生成响应阶段。
2.8.6 生成响应阶段
在这个阶段,应用准备向客户端输出响应,如果应用使用JSP页面,那么JSF将调用JSP容器来处理响应。如果请求是一个初始请求,页面上的组件将被JSP容器添加到组件树中;如果请求不是初始请求,那么所有组件都已被添加到组件树中,因此不需要再次添加。
如果是提交表单的请求,并且在应用请求值阶段、处理输入校验阶段或更新模型的值阶段遇到了错误,应用将会选择生成最初的页面。如果页面中包含<h:message…/>、<h:messages…/>标签,那就可以在页面上显示错误消息。
生成响应完成之后,应用的响应状态也被保存下来,这样使得后续的请求可以访问该状态。
JSF提供了PhaseId类来代表生命周期阶段,该类本质上是一个枚举类(虽然JSF并未使用enum来定义它),因此程序中常常使用的是它的7个常量:
ANY_PHASE:该常量代表任意一个生命周期阶段。
APPLY_REQUEST_VALUES:该常量代表应用请求值的生命周期阶段。
INVOKE_APPLICATION:该常量代表调用应用的生命周期阶段。
PROCESS_VALIDATIONS:该常量代表处理输入校验的生命周期阶段。
RENDER_RESPONSE:该常量代表生成响应的生命周期阶段。
RESTORE_VIEW:该常量代表恢复视图的生命周期阶段。
UPDATE_MODEL_VALUES:该常量代表更新模型的值的生命周期阶段。
如果应用需要准确取得JSF应用当前所处的生命周期阶段,就可通过上面7个常量来准确识别应用当前所处的生命周期阶段。