kidneyball 发表于 2013-1-29 11:33:59

JSF 2.0阅读笔记:视图状态 (二)

(续一)

三、可能的优化方案

撇开JSF2.0不谈,不妨先来想想针对JSF1.2的问题,有哪些可能的优化方案?

1. 组件的底层实现应改用可统一管理的,按名访问的,动态的弱类型属性集合。说白了,就是在组件类中用一个统一的Map来管理组件属性值。相对于强类型对象域,这种方法虽然在理论上会导致属性访问性能降低,但同时会给采集组件状态的性能带来提升,因为现在只需要采集被显式设置过的组件属性,而不是组件上的所有属性。进而,对后续的编码/解码速度,视图状态大小等等性能参数带来提升。并且,可以对saveState与restoreState的逻辑进行抽象,提升到继承层次更高的基类中甚至API中实现,而不需每个具体的组件类自行去覆盖。

2. API支持与业务逻辑无关的视图声明语言(例如Facelets),JSF引擎通过视图定义语言可随时获取到视图的初始状态。基于初始状态,对需要实际保存的视图状态进行优化。优化的方案有很多种,包括:

a. 增量视图状态。视图状态中仅保存当前组件树状态与初始状态的差异信息。在恢复组件树时,先通过视图声明语言获得视图初始状态,再根据视图状态中的差异信息进行调整,最终获得实际的当前组件树状态。这种方案的优点是在保持对应用开发者隐藏视图状态概念的同时,大幅降低了仅对组件树作少量局部修改的场景下的视图状态尺寸。对于动态性较强的页面,例如页面上仅放一个f:view,整棵组件树都是在运行期动态生成的场景,则提升不大。

b. 局部视图状态。视图状态中仅补存当前组件树状态的局部信息。在恢复组件树时,先通过视图声明语言获得视图初始状态,在根据视图状态中的局部信息进行调整,最终获得实际的当前组件树状态。这种方案需要把视图状态的概念暴露给应用开发者,应用开发者需要通过某种方式在组件树中指定需要保存的部分,并且必须清楚认识到,对于不保存的部分,每次请求开始时都会恢复为初始状态,在请求后续处理中所作的修改不能保留到下一请求中。此外,对于某些功能复杂的组件,很可能存在一些属性值是必须跨请求保持的,否则将导致其无法正常工作,例如说分页组件的当前页数。对于这些必须保存的状态,引擎需要提供机制上的支持,并且提供接口让组件开发者能方便地定义这些必须保存的属性。显然,这种方案会侵入组件开发者和应用开发者现有的编程模型,迫使他们必须关注视图状态的概念。但是,优势也是明显的,应用开发者拥有了对视图状态的完全控制能力。在大多数视图状态不会发生改变场景中,他可以指定根本不保存视图状态。

c. 分离视图状态。前面说过,JSF1.2支持让开发者选择把视图状态保存在服务器端还是客户端。在实际应用中,这种非黑即白的二分法只会让开发者顾此失彼,要么完全牺牲内存,要么完全牺牲带宽。但话又说回来,这种“以带宽换内存”的思路大体上是合理的,前提是,在换的比例上应能平滑过渡,而不是非此即彼。比如说,对于带宽吃紧的环境,应允许配置为“最多允许100k的视图状态传递到客户端,超出的保存在服务器端session中”,或者“40%的视图状态传递到客户端,60%保存在服务器端session中”等等。

需要注意的是,以上的a、b、c三种方案,并不是互斥的,完全可以结合起来使用。例如可以有局部增量视图状态,也可以把增量视图状态分离保存。

四、JSF2.0中的视图状态

现在开始进入正题(传说中的三纸无驴?),JSF2.0提供了怎样的视图状态方案。

简单来说,JSF2.0提供了上面优化方案中的1和2a,也就是统一的弱类型属性集合,和增量视图状态。但其中增量视图状态在规范中被称为PartialState(局部状态),为了避免歧义,下文中仍然称其为增量视图状态。

结合JSF 2.0的API与参考实现(Mojarra项目)来看,与视图状态相关的类关系大致如下:

http://dl.iteye.com/upload/attachment/376763/03ca9d6c-ba3c-3bbc-a45d-242581bb097f.png

其中,绿色为API中的接口和基类,黄色为RI中的实现类。

图中可以看出JSF 2.0中视图状态处理的主线流程。前面提到过,StateManager是视图状态处理的主入口。在图中可以看出,RI中有两个时机触发生成视图状态信息。一是在Form组件的渲染器中,调用ViewHandler的writeState方法,该方法则负责调用StateManager的writeState方法进入生产视图状态的流程。当页面刷新时,走这条路径。二是在PartialViewContext的processPartial方法中,直接调用StateManager的getViewState方法获取已编码的视图状态信息字符串。当渲染Ajax响应时,走这条路径。

StateManager的实现类主要负责视图状态的收集和恢复过程。针对生产视图状态,StateManager提供了writeState与getViewState两个接口方法,其中writeState负责在收集好当前组件树状态信息后,调用ResponseStateManager进行编码和输出,而getViewState方法则只以字符串方式返回编码后的视图状态信息,并不实际向响应流中输出。

在JSF2.0之前的版本,收集和恢复的逻辑直接由StateManager的实现类负责。JSF2.0中加入了VDL的概念,因此在这里把收集和恢复代理给当前ViewDeclarationLanguage所持有的StateManagementStrategy实例来完成。换句话说,通常情况下在JSF2.0中视图状态的收集和恢复逻辑的入口,在StateManagementStrategy的实现类中;编码和解码逻辑的入口,在ResponseStateManager的实现类中。

StateManagementStrategy提供了两个抽象方法:

public abstract class StateManagementStrategy {    public abstract Object saveView(FacesContext context);    public abstract UIViewRoot restoreView(FacesContext context, String viewId,                                           String renderKitId);}

其中saveView方法的实现负责遍历组件树。把组件树的当前状态收集到一个可序列化数据结构中返回。在JSF2.0之前版本,API上硬编码了该结构至少应包括组件树结构(TreeStructure),组件属性(Attributes),与附加对象(AttachedObject)三部分。从JSF2.0版本开始,在API代码层面上去除了对该数据结构的限制。

restoreView方法则负责从视图状态中恢复出组件树。对于这个方法,API通过javadoc详细指明了实现步骤。实现类应先从页面(markup)中构造出原始的组件树,对于自动生成的组件id,必须与首次请求时所生成的id一致。然后调用ResponseStateManager上的getState方法(解码)获取到与上次请求中调用saveView方法所返回的数据结构一致的视图状态信息。最后,根据这个视图状态信息对先前构造的原始组件树进行调整,最终得出实际的组件树,返回其根节点。

从这里的javadoc可以看出,JSF2.0 API所推荐的视图状态方案,就是增量方案。顺带一提,我个人觉得在API的javadoc上写这种实现细节并不妥当,难道别人的实现不用增量视图状态就不符合规范了吗?说不定有比增量方案更好的呢?

以上是处理视图状态的主线流程,下面具体来看看JSF2.0是如何实现弱类型属性集合和增量视图状态的。

4.1. 弱类型属性集合

从上面的类图中的“组件状态关系结构”部分可以看出,所有组件(UIComponent是所有组件的公共基类)都实现了PartialStateHolder接口,而PartialStateHolder又继承自StateHolder接口。这个StateHolder是什么?简单来说,就是希望在JSF视图状态中保存自身状态,并且希望控制自身状态的采集和恢复过程的类必须实现的接口。StateHolder提供了两个经典接口方法:
public Object saveState(FacesContext context);public void restoreState(FacesContext context, Object state);

StateManagementStrategy在收集视图状态时,事实上是通过遍历组件树,调用各个组件上的saveState方法来获取组件的当前状态的。因此组件可以覆盖此方法,控制自身的状态应包括哪些属性值和保存的方式。restoreState则是saveState的逆操作,负责根据上一次调用saveState所收集的组件状态数据,来恢复组件的属性值。

StateHolder对于组件开发者来说是一个非常重要的接口,除了组件以外,任何希望在视图状态中保存的附加对象,都可以考虑实现该接口,例如Validator等等。JSF规范中规定(Sec7.7.1)

<div class="quote_title">引用
页: [1]
查看完整版本: JSF 2.0阅读笔记:视图状态 (二)