【问题标题】:Why does JSF save component tree state?为什么 JSF 保存组件树状态?
【发布时间】:2011-11-13 00:46:35
【问题描述】:

托管 bean 状态和组件树状态之间似乎存在差异。您可以通过使用 @RequestScoped@SessionScoped 等注解来控制托管 bean 状态,但您似乎无法选择是否保存组件树状态(虽然你可以选择是保存在服务器还是客户端)。

似乎组件树状态应该只在单个请求期间作为临时数据结构来帮助处理请求。它应该为每个请求从头开始重建。使用 JSF 2.0 部分状态保存使情况变得更好,因为只保存了表单数据,但我不明白为什么连上一个请求中的表单数据都有用。

如果您的应用程序仅使用请求范围托管 bean,那么在请求之间保存组件树状态尤其没有意义。即使您的应用程序具有会话范围托管 bean,我也会假设托管 bean 将保持状态,并且组件树仍然不需要在请求之间具有任何状态。

【问题讨论】:

    标签: jsf jsf-2 state-saving


    【解决方案1】:

    除了上一个答案之外,自 JSF 2.0 以来,默认使用名为 partial state saving 的东西。

    JSF (Facelets) 中的默认视图描述语言在每次请求后从原始 Facelet 创建整个组件树,并根据相应的标签属性初始化组件。然后它标记状态。

    然后,每个后续状态更改都会被记住为增量更改,并且实际保存的是该状态。很可能结果是根本没有这样的变化,然后视图状态为空(由于一个错误,状态从未真正为空,但最近已修复。有关详细信息,请参阅http://java.net/jira/browse/JAVASERVERFACES-2203

    所以最大的问题是,当它不是空的时候,这个状态实际上是什么?

    正如 BalusC 已经指出的那样,这可以对组件树进行动态更改。这些更改既可以从支持 bean 启动,也可以从静态组件中启动。进行这种动态更改的组件类型的一个简单示例是表组件,它根据数据集中的实际列数创建子列组件。

    视图状态的另一个重要用途是记住在组件内部已更改但尚未推送到模型中的值。这可以是轻弹开关组件中的开关、移动拨号组件中的滑块等。

    一个特别的例子是viewParam 组件,它会记住初始化它的请求参数(GET 的查询字符串参数或非面孔 POST 参数)。有关更多信息,请参阅此内容:http://arjan-tijms.omnifaces.org/2011/07/stateless-vs-stateful-jsf-view.html

    还与有状态组件记住 UI 状态和转换或验证失败有密切关系。在这种情况下,UI 组件会记住用户输入的值,并且会记住存在转换/验证错误。

    状态的另一个用途是优化。一些组件计算他们认为计算成本高的值并将这些值存储在视图状态中。例如,UIInput 组件在第一次回发后执行此操作:

    private boolean validateEmptyFields(FacesContext ctx) {
    
        if (validateEmptyFields == null) {
            ExternalContext extCtx = ctx.getExternalContext();
            String val = extCtx.getInitParameter(VALIDATE_EMPTY_FIELDS_PARAM_NAME);
    
            if (val == null) {
                val = (String) extCtx.getApplicationMap().get(VALIDATE_EMPTY_FIELDS_PARAM_NAME);
            }
            if (val == null || "auto".equals(val)) {
                validateEmptyFields = isBeansValidationAvailable(ctx);
            } else {
                validateEmptyFields = Boolean.valueOf(val);
            }
        }
    
        return validateEmptyFields;
    
    }
    

    validateEmptyFields 存储在视图状态之后,因此在以下表单提交时不必再次计算。如果用户可以在重新计算或存储(众所周知的时空优化)之间进行选择,那将是一种改进。

    状态的概念自早期概念以来就一直困扰着 Web 应用程序开发。每个人都希望有本质上是有状态的交互,但几乎没有人想要处理它甚至想它。

    JSF 一直试图在这里提供答案,但显然并不完美,还有改进的余地。 JSF 坚持能够恢复视图状态(甚至是空视图状态)可能会很麻烦,尽管正如另一个答案中提到的那样,它确实提供了针对 CSRF 的隐式保护。 JSF 2.2 将获得更明确的 CSRF 保护(参见例如http://arjan-tijms.omnifaces.org/p/jsf-22.html#869),所以也许我们将来会在这里看到一些变化。

    有一个关闭每个组件状态的选项并有一个简单的钩子来恢复状态以防框架不能(如在 ASP.NET 中)也可能会有所帮助。

    【讨论】:

    • 感谢您添加更多说明和示例。我现在明白了一点。我仍然不相信保存组件树状态是正确的做法,因为您似乎总是可以使用托管 bean 工具来保存状态。我不明白为什么必须将状态保存到重新显示用户输入的未通过转换/验证的值 - 只是服务器端将帖子转发到原始视图。
    • 有时视图状态的使用确实值得商榷,我准备为 EG 创建一些 JIRA 问题来解决这个问题。然而,托管 bean 并不总是正确的答案。许多此类状态涉及组件的内部细节以及处理此问题的负担,恕我直言,如果不对用户强制执行。我还没有提到的另一个类别是优化(请参阅更新的答案)
    【解决方案2】:

    因为可以根据初始请求以编程方式更改组件树。每当必须处理表单数据时,这不一定可以在后续请求中重现。

    此外,我的印象是您认为组件树也包含模型值。这不是真的。它只保存对模型值(托管 bean 属性)的引用(通过表达式语言)。视图状态不复制/复制/包含模型状态。它只是一个纯 UI 组件树。也许您的困惑是基于此。请注意,术语“表单数据”将被解释为提交的值和模型值。

    另见:

    【讨论】:

    • 您能否提供一个没有先前请求状态无法重建组件树的示例?
    • 这些组件可以通过UIViewRoot#findComponent() 访问并通过例如程序以编程方式更改。 component.setAttribute("foo", "bar") 或新的孩子可以通过例如添加component.getChildren().add(newHiddenInput) 等等。这可能发生在托管 bean 中,或者更常见的是 PhaseListener。诚然,这不一定发生在使用基本 JSF 的琐碎形式中,但这可能发生在更复杂的形式中或更高级的组件库中。如果您重新创建新视图,而不是使用之前请求的呈现响应期间的状态,那么所有这些更改都会丢失。
    • 双重提交预防令牌可以保存在带有 ViewScope 的托管 bean 中或直接保存在 HTTPSession 中,因此听起来组件树不应该在请求之间保存。我浏览了 JSF 规范、Ed Burns 的 JSF 2.0 Complete Reference Book 和 Cay Horstmann 的 Core JSF 3rd Edition 书,但没有找到一个解释为什么必须保存组件树。奇怪吧?
    • @Ryan 我什至找不到一点优势! :) 你能告诉我这是什么吗???
    • @M.ES - 这对我来说也没有多大意义。我承认您可以构建一个场景,其中组件树保存将来请求中使用的状态,但在我看来,在这些场景中将状态放在托管 bean 设施中会更好。我猜推理只是因为用户可以
    猜你喜欢
    • 2014-06-15
    • 2011-07-25
    • 2014-02-23
    • 2012-10-12
    • 2016-05-03
    • 1970-01-01
    • 2019-07-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多