`
kidneyball
  • 浏览: 329016 次
  • 性别: Icon_minigender_1
  • 来自: 南太平洋
社区版块
存档分类
最新评论

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

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

在StateHelper的javadoc上明确声明:
引用
Define a Map-like contract that makes it easier for components to implement PartialStateHolder. Each UIComponent in the view will return an implementation of this interface from its UIComponent.getStateHelper() method.


可以看出,这是一个“Map-like”的通过key-value方式访问的接口,它不仅仅是组件属性的底层储存结构,还为组件的增量状态(PartialState)提供支持。组件上提供getStateHelper方法,用于返回本组件持有的StateHelper实例。我们观察任意一个API基础组件,例如UIInput,可以发现其属性(例如说,immediate属性)是这样实现的:

    public boolean isImmediate() {
        return (Boolean) getStateHelper().eval(PropertyKeys.immediate, false);
    }


    public void setImmediate(boolean immediate) {
        getStateHelper().put(PropertyKeys.immediate, immediate);
    }


作为比较,不妨回头来看看JSF1.2 API中的UIInput:
    /**
     * <p>The immediate flag.</p>
     */
    private boolean immediate = false;
    private boolean immediateSet = false;


    public boolean isImmediate() {
        if (this.immediateSet) {
            return (this.immediate);
        }
        ValueExpression ve = getValueExpression("immediate");
        if (ve != null) {
            try {
                return (Boolean.TRUE.equals(ve.getValue(getFacesContext().getELContext())));
            }
            catch (ELException e) {
                throw new FacesException(e);
            }

        } else {
            return (this.immediate);
        }
    }


    public void setImmediate(boolean immediate) {
        // if the immediate value is changing.
        if (immediate != this.immediate) {
            this.immediate = immediate;
        }
        this.immediateSet = true;
    }


可以看出使用强类型对象域(JSF1.2)与弱类型属性集合(JSF2.0)作为底层储存结构所带来的明显区别。同样,在JSF2.0中,具体组件的saveState/restoreState方法实现也变得相对简单,以UIInput为例:
    private Object[] values;

    public Object saveState(FacesContext context) {
        if (context == null) {
            throw new NullPointerException();
        }
        if (values == null) {
            values = new Object[4];
        }

        values[0] = super.saveState(context);  //将间接调用到UIComponentBase.saveState方法
        values[1] = emptyStringIsNull;
        values[2] = validateEmptyFields;
        values[3] = ((validators != null) ? validators.saveState(context) : null);
        return (values);
    }

可以看出,现在具体组件的saveState/restoreState方法中,只需要负责保存一些内部对象域的状态。而数目众多的组件属性,则无须在这里显式保存。如果具体组件中没有内部对象域需要保存,甚至可以不覆盖saveState/restoreState方法,例如UICommand组件。因为针对StateHelper的save/restore逻辑,写在了API组件的公共基类UIComponentBase(自定义组件亦可从这里开始继承)中。反观JSF1.2中的UIInput:
    public Object saveState(FacesContext context) {
        if (values == null) {
            values = new Object[14];
        }
        values[0] = super.saveState(context);
        values[1] = localValueSet ? Boolean.TRUE : Boolean.FALSE;
        values[2] = required ? Boolean.TRUE : Boolean.FALSE;
        values[3] = requiredSet ? Boolean.TRUE : Boolean.FALSE;
        values[4] = requiredMessage;
        values[5] = requiredMessageSet ? Boolean.TRUE : Boolean.FALSE;
        values[6] = converterMessage;
        values[7] = converterMessageSet ? Boolean.TRUE : Boolean.FALSE;
        values[8] = validatorMessage;
        values[9] = validatorMessageSet ? Boolean.TRUE : Boolean.FALSE;
        values[10] = this.valid ? Boolean.TRUE : Boolean.FALSE;
        values[11] = immediate ? Boolean.TRUE : Boolean.FALSE;
        values[12] = immediateSet ? Boolean.TRUE : Boolean.FALSE;
        values[13] = saveAttachedState(context, validators);
        return (values);
    }


高下立见。

4.2 增量视图状态

JSF2.0中的增量视图状态处理的核心,是由StateManagementStrategy的实现类、PartialStateHolder接口、StateHelper的实现类三者配合组成的。

首先,在StateManagementStrategy的接口javadoc上,明确规定了增量式收集与恢复视图状态的总体思路:
saveState方法:
引用

public abstract Object saveView(FacesContext context)

    Return the state of the current view in an Object that implements Serializable. The default implementation must perform the following algorithm or its semantic equivalent.

        1. If the UIViewRoot of the current view is marked transient, return null immediately.

        2. Traverse the view and verify that each of the client ids are unique. Throw IllegalStateException if more than one client id are the same.

        3. Visit the tree using UIComponent.visitTree(javax.faces.component.visit.VisitContext, javax.faces.component.visit.VisitCallback). For each node, call StateHolder.saveState(javax.faces.context.FacesContext), saving the returned Object in a way such that it can be restored given only its client id. Special care must be taken to handle the case of components that were added or deleted programmatically during this lifecycle traversal, rather than by the VDL.

    The implementation must ensure that the StateHolder.saveState(javax.faces.context.FacesContext) method is called for each node in the tree.

    The data structure used to save the state obtained by executing the above algorithm must be Serializable, and all of the elements within the data structure must also be Serializable.

    Parameters:
        context - the FacesContext for this request.
    Since:
        2.0


restoreState方法:
引用

public abstract UIViewRoot restoreView(FacesContext context,
                                       String viewId,
                                       String renderKitId)

    Restore the state of the view with information in the request. The default implementation must perform the following algorithm or its semantic equivalent.

        1. Build the view from the markup. For all components in the view that do not have an explicitly assigned id in the markup, the values of those ids must be the same as on an initial request for this view. This view will not contain any components programmatically added during the previous lifecycle run, and it will contain components that were programmatically deleted on the previous lifecycle run. Both of these cases must be handled.

        2. Call ResponseStateManager.getState(javax.faces.context.FacesContext, java.lang.String) to obtain the data structure returned from the previous call to saveView(javax.faces.context.FacesContext).

        3. Visit the tree using UIComponent.visitTree(javax.faces.component.visit.VisitContext, javax.faces.component.visit.VisitCallback). For each node, call StateHolder.restoreState(javax.faces.context.FacesContext, java.lang.Object), passing the state saved corresponding to the current client id.

        4. Ensure that any programmatically deleted components are removed.

        5. Ensure any programmatically added components are added.

    The implementation must ensure that the StateHolder.restoreState(javax.faces.context.FacesContext, java.lang.Object) method is called for each node in the tree, except for those that were programmatically deleted on the previous run through the lifecycle.

    Parameters:
        context - the FacesContext for this request
        viewId - the view identifier for which the state should be restored
        renderKitId - the render kit id for this state.
    Since:
        2.0


简而言之,API规定了,在收集视图状态时,不管具体算法如何,但必须保证以下4点:1.调用组件上的saveState方法获取组件状态;2.视图状态的组织形式必须保证通过组件的clientId能唯一查找到该组件的状态信息;3.对于运行时动态添加和删除的组件(修改了组件树结构),要在视图状态中记录下来;4.最终形成的视图状态数据结构必须可序列化。

在恢复视图状态时,首先要通过原始页面(markup)构建出原始状态的组件树,特别地,组件树的结构和组件id必须与首次请求所创建的组件树完全一致。然后,遍历组件树,通过组件的clientId在视图状态中提取出该组件的状态信息,并调用组件上的restoreState方法恢复组件状态。再然后,删去在视图状态中记录的被动态删除了的组件。最后,添加在视图状态中记录的被动态添加的组件。

这样,StateManagementStrategy定义了在组件层面的增量视图状态处理方案。首先,它保证了组件状态的相互独立性,如果一个组件相对于原始状态没有变更,只要它的clientId不出现在视图状态中即可。其次,它保证了组件树结构的动态改动被独立记录和恢复。

而组件属性层面的增量状态,则是由PartialStateHolder与StateHelper共同提供的。组件的公共基类UIComponent实现了PartialStateHolder接口,该接口为组件提供了以下接口方法:
void markInitialState();

boolean initialStateMarked();

void clearInitialState();


它的设计思路是,实现这个接口的组件被分为两个状态,一个叫初始态(InitialState),另一个规范中没命名,不妨称之为增量态。JSF引擎应该保证,在根据视图声明语言创建组件实例、注入声明语言中定义的初始属性值时,组件处于初始态。在这之后,组件属性可能被动态改动之前,JSF引擎调用组件上的markInitialState方法,使组件进入增量态。处于增量态的组件,将记录自身属性的变动,从而允许在收集增量视图状态时,只包含差异属性。在RI中,大部分组件的markInitialState方法在Facelets解析过程中,组件被加入组件树并注入属性值后立刻调用:
package com.sun.faces.facelets.tag.jsf;

public class ComponentTagHandlerDelegateImpl extends TagHandlerDelegate {
...
    public void apply(FaceletContext ctx, UIComponent parent) throws IOException {
        ...
	c = this.createComponent(ctx);
        ...
        // add to the tree afterwards
        // this allows children to determine if it's
        // been part of the tree or not yet
        addComponentToView(ctx, parent, c, componentFound);
        popComponentFromEL(ctx, c, ccStackManager, compcompPushed);

        if (shouldMarkInitialState(ctx.getFacesContext())) {
            c.markInitialState();
        }
        
    }
...
}


当组件进入增量态后,记录差异属性的责任就落在属性值的储存结构 —— StateHelper —— 头上了。这从StateHelper的默认实现类ComponentStateHelper可以清楚地看出:
class ComponentStateHelper implements StateHelper {
...
    private UIComponent component;
    private Map<Serializable, Object> deltaMap;
    private Map<Serializable, Object> defaultMap;
...
    public Object put(Serializable key, Object value) {

        if(component.initialStateMarked() || value instanceof PartialStateHolder) {
            Object retVal = deltaMap.put(key, value);

            if(retVal==null) {
                return defaultMap.put(key,value);
            }
            else {
                defaultMap.put(key,value);
                return retVal;
            }
        }
        else {
            return defaultMap.put(key,value);
        }
    }
...
    public Object saveState(FacesContext context) {
        if (context == null) {
            throw new NullPointerException();
        }
        if(component.initialStateMarked()) {
            return saveMap(context, deltaMap);
        }
        else {
            return saveMap(context, defaultMap);
        }
    }


ComponentStateHelper持有两个Map,defaultMap与deltaMap。从put方法的代码可以看出,无论任何时候,新值都会被保存到defaultMap中。但当所属组件处于增量态时,新值将被同时保存到deltaMap中。在收集视图状态时,saveState方法被调用,如果组件处于增量态,则只返回deltaMap中的差异属性状态。如果对于个别组件需要保存完全状态,只需要在进入收集视图状态之前先调用组件上的clearInitialState(),让组件回到初始态,则返回defaultMap中的完全组件状态。

这种通过put方法监视组件属性变化的方式有一个比较大的缺陷,就是无法监视属性值内部的变化。最常见的场景是,如果属性值是一个集合类型,对集合元素的改动是不会作为增量变动被记录下来的。为此,JSF2.0提供了一个折中方案,在StateHelper接口上针对Map和List提供了专门的方法
Object put(Serializable key, String mapKey, Object value);

void add(Serializable key, Object value);
Object remove(Serializable key, Object valueOrKey);


其中,三参数的put方法针对类型为Map的属性,调用该方法将获取到一个名称为key,类型是Map<String, Object>的属性,如果没有该属性则创建之。并在其中加入键值为mapKey,值为value的元素。同理,双参数的add与remove方法则针对类型为List的属性。以上三个方法均应保证对增量视图状态的处理。当然,这只是个解决大部分场景的不完全解决方案,比如说我要有个类型是Set的属性,就没招了,只能扩展StateHelper接口来实现。感觉上API上带了这三个方法主要还是起到示范作用。
3
0
分享到:
评论

相关推荐

    一个简单的jsf例子------JSF2学习笔记1

    **JSF2学习笔记1——理解JavaServer Faces 2.0框架** JavaServer Faces (JSF) 是一种基于组件的Web应用程序开发框架,由Sun Microsystems(现为Oracle Corporation的一部分)开发,旨在简化用户界面构建。JSF2是该...

    良葛格_JSF学习笔记.rar

    `FacesContext`是JSF框架的核心上下文对象,它在JSF生命周期中贯穿始终,提供了对请求、响应和应用程序状态的访问。Managed Beans是JSF中的业务逻辑组件,通过`@ManagedBean`和`@RequestScoped`等注解来声明和管理。...

    《JSF入门》简体中文版.rar

    而《Garfield的SCJP阅读笔记》.rar文件可能是关于Sun Certified Programmer for the Java SE Platform(SCJP)考试的学习资料,SCJP是Java开发者的基础认证,对于深化Java语言的理解大有裨益,但与JSF主题不直接相关...

    JavaEE5实战笔记04JSF的一些补充

    本篇实战笔记主要补充了在JavaEE5中使用JSF的一些关键点,特别是关于流程转向和界面参数传递。 1. **流程转向**: 在JSF中,流程转向通常通过`faces-config.xml`文件中的`&lt;navigation-rule&gt;`元素来定义。例如,当...

    jee6 学习笔记 5 - Struggling with JSF2 binding GET params

    在Java企业版(Java EE)6的开发过程中,JSF(JavaServer Faces)2.0是用于构建用户界面的重要组件。这篇"jee6 学习笔记 5 - Struggling with JSF2 binding GET params"主要探讨了开发者在使用JSF2绑定GET参数时可能...

    JSF+Spring+Hibernate相关技术文档

    JSF 2.0规范(其中包含的JSF_20规范.pdf和JSF.pdf文档可能详细阐述了这一版本的特性)引入了许多改进,如异步处理、更强大的Facelets视图技术以及对CDI(Contexts and Dependency Injection)的支持,使得开发者可以...

    【读书笔记】Java参考大全-J2EE5版本

    JSF2.0在J2EE5中引入,进一步增强了其功能,如Facelets视图技术、复合组件支持和AJAX集成。 5. **持久化API (JPA)**:JPA是Java Persistence API,它是J2EE5的一部分,提供了一种标准的方式来管理和持久化Java对象...

    清晰的技术资料学习笔记

    文件可能包含了SQL的基本语法、查询操作、数据更新、表的创建与修改、视图、存储过程等内容,对于初学者和数据库管理员非常有用。 2. **IT日本語.doc**:这可能是关于IT相关的日语词汇或术语文档,适合那些需要在...

    j2ee体系chm帮助文档大全

    这个压缩包里涵盖了从基础到高级的各种主题,包括Hibernate学习笔记、J2EE全实例教程、JSF中文教程、Java设计模式、XML指南以及Struts2.0中文帮助手册等,对于想要深入理解J2EE框架和技术的开发者来说,无疑是一份...

    appfuse 学习笔记

    AppFuse 是一个开源项目,旨在加速和简化J2EE应用程序的开发流程。由Matt Raible设计,它作为一...通过深入阅读和实践AppFuse的学习笔记,开发者可以逐步掌握其核心功能,并将其应用到实际项目中,提升开发质量和效率。

    SCWCD-083资料

    3. **EL(Expression Language)**:EL是JSP 2.0引入的简洁表达式语言,用于访问JavaBean属性和执行基本操作。掌握EL变量,操作符,函数和上下文对象的使用。 4. **JSTL(JavaServer Pages Standard Tag Library)*...

Global site tag (gtag.js) - Google Analytics