3

很抱歉没有在专门的测试用例中抽象出这个问题,我希望一个真实项目的例子足够简单来描述这个问题。

我有一个 JavaEE/JPA2/JSF Web 应用程序,其中每个 @Entity 元素(或子类)都有一个模板化的 view.xhtml 页面和一个标准的链接生成器复合组件 util:view_link.xhtml,以 GET 形式调用,数据库 ID 作为参数。每个视图页面的一部分(仅)代表一个专家系统摘要;该部分可以抽象为复合组件,以包含在视图页面或其他地方。

我引入了 Primefaces p:dialog 模式弹出窗口,用于在单击视图链接旁边显示的小状态图标时显示专家系统摘要部分(以及任何其他诊断)。如果你让状态图标用 x 表示,它看起来像这样:

x Link_to_Element_by_ID

单击“Link_to_Element_by_ID”,它会显示完整的视图页面。

单击“x”图标(专家系统测试失败指示器),它会弹出带有专家系统摘要的 p:对话框(仅)。

因此,视图页面的专家系统部分作为复合组件共享。

但这可能导致递归和 Stackoverflow,如果:

  1. 弹出的 p:dialog 专家系统摘要显示正在检查的元素的状态图标指示器。

  2. 我包括附加的元素视图链接以及状态指示器(它们本身会启动 ap:dialog 以获取专家系统摘要)。

我曾尝试使用递归阻塞属性“preventRecursionOnDialog”来使用渲染测试,但它失败了,显然是因为递归是在构建阶段发生的。

问:如何使用测试变量阻止可能的递归?

另外,我尝试过 c:if 测试而不是 JSF 'rendered' 测试,但似乎测试的变量在@ViewScoped 下不可用。

例如,对于 Activity 元素,其中 util_primefaces:dialog_summary 只是 ap:dialog 的自定义封装。

来自 util:status_activity.xhtml:

     <composite:attribute 
        name="activity"
        required="true"
        type="com.example.entity.Activity"
      />
    <composite:attribute
        name="preventRecursionOnDialog"
        required="false"
        default="false"
        type="java.lang.Boolean"
        />
</composite:interface>

<composite:implementation> 
    <util_primefaces:dialog_summary 
        header="Expert system summary report"
        rendered="#{not cc.attrs.preventRecursionOnDialog}"
        element="#{cc.attrs.activity}">

        <!-- causes StackOverflowError -->

        <util:warn_insufficient_subactivities 
            activityContainer="#{cc.attrs.activity}"
            humanTypeDescription="composite activity"
            preventRecursionOnDialog="true"
            />

        <util:expertsystem_activity activity="#{cc.attrs.activity}"/>

    </util_primefaces:dialog_summary>
    ..
    <span 
        onclick="#{not cc.attrs.preventRecursionOnDialog ? ('dialog'.concat(cc.attrs.activity.id).concat('.show();')) : ''}" 
        style="float:left;" 
        class="icon-completed-#{cc.attrs.activity.acceptedEffective}-small"
        title=".."
        >&nbsp;</span>

util:warn_insufficient_subactivities(显示复合活动的哪些子活动未通过专家系统测试)可能导致递归:

    <cc:interface>
    <cc:attribute name="activityContainer" required="true" type="com.example.entity.IActivityContainer"/>
    <cc:attribute name="humanTypeDescription" required="true" type="java.lang.String"/>
    <cc:attribute
        name="preventRecursionOnDialog"
        required="false"
        default="false"
        type="java.lang.Boolean"
        /> 
</cc:interface> 

<cc:implementation>     
    <h:panelGroup 
        rendered="#{not cc.attrs.activityContainer.sufficientSubActivitiesAccepted}">
        <util:warn_box 
            message=".."
            >
            <!-- CAUTION: can cause Stackoverflow when list included in expertsystem p:dialog popup -->
            <util:list_activity_compact 
                list="#{cc.attrs.activityContainer.activities}" 
                preventRecursionOnDialog="#{cc.attrs.preventRecursionOnDialog}"
                rendered="#{not cc.attrs.preventRecursionOnDialog}"
                />                
        </util:warn_box>

并且 util:list_activity_compact 显示了一个带有状态图标指示器的列表(这反过来可以提供一个带有专家系统摘要的弹出 p:dialog,并且可以递归)和 util:view_link:

    <cc:interface>

    <cc:attribute 
        name="list" required="true" type="java.util.List"
        />

    <cc:attribute
        name="preventRecursionOnDialog"
        required="false"
        default="false"
        type="java.lang.Boolean"
        />

</cc:interface>

<cc:implementation>
    <h:panelGroup display="block">
        <ul class="view-field-list-medium">
            <ui:repeat var="a" value="#{cc.attrs.list}">
                <li class="view-field-list">
                    <util:status_activity 
                        activity="#{a}" 
                        preventRecursionOnDialog="#{cc.attrs.preventRecursionOnDialog}"/> 
                    <util:view_link element="#{a}"/>
                </li>
            </ui:repeat>
        </ul>
    </h:panelGroup>
</cc:implementation>

问题的关键是,即使未渲染要递归的部分(被渲染的测试阻止),测试渲染=“#{not cc.attrs.preventRecursionOnDialog}”也不足以阻止递归在 JSF 构建阶段仍然可能发生递归。


顺便说一句,当我只想在类型选择的子集中呈现绑定到类型的特定复合组件时,我经常遇到类似的问题;在“渲染”中执行类型测试不足以防止类型错误。想象一下,“值”可能是包括 Activity 在内的许多 Element 子类之一,但只想显示 Activity 的以下复合组件部分:

        <util:component_for_Activity_only 
            activity="#{cc.attrs.value}"
            rendered="#{cc.attrs.value['class'].simpleName=='Activity'}"
            />

(参见EL 表达式语言中的 instanceof 检查,并注意基于 Class String 的类型测试解决方案不是很灵活,它不适用于子类或接口测试。)

同样,尝试使用“rendered”阻止调用是不够的,似乎类型测试在构建阶段已经失败。递归问题的解决方案也将为此提供解决方案。即使在 JSF2 中引入(最终) instanceof (请在此处投票http://java.net/jira/browse/JSP_SPEC_PUBLIC-113)如果仅用于“渲染”,也无济于事,

4

2 回答 2

4

在进一步研究和试验后回答自己的问题。

首先,感谢meriton,您的回答并没有完全回答我的问题,而是让我走上了正确的道路。

简短的回答是,自 Mojarra 2.1.18 起,您可以在 @ViewScoped 的构建阶段使用 c:if 测试来控制递归。就我而言,这现在有效:

        <c:if test="#{not cc.attrs.preventRecursionOnDialog}">

通常,BalusC 对其他帖子的贡献使我得到了这个答案(需要升级到 Mojarra >= 2.1.18 以及关于改进@ViewScoped 中 JSTL 标签库处理的解释),包括:

https://java.net/jira/browse/JAVASERVERFACES-1492

JSF2 Viewscope 验证问题

http://balusc.blogspot.com.au/2010/06/benefits-and-pitfalls-of-viewscoped.html

JSF2 Facelets 中的 JSTL...有意义吗?

Java Server Faces 2.0 的主要缺点是什么?

我认为这对任何使用 JSF 的人来说都非常重要!在 @ViewScoped 中轻松控制构建的内容(而不是渲染的内容)的能力解决了许多问题并开辟了许多可能性,我建议任何认真使用 JSF 的人花时间阅读 BalusC 在以上链接。

BalusC,如果你读到这里,请知道你是 JavaServer Faces 和 Enterprise Java 的真正英雄。我代表每一位 JSF 爱好者向你们表示感谢。

感谢 Ed Burns 和 Ted Goddard 在报告和修复此问题方面所做的出色工作:https : //java.net/jira/browse/JAVASERVERFACES-1492 这是 JSF2 的一项重大改进。

最后,我不得不使用一个肮脏的技巧来在 NetBeans7.1+Glassfish3.1.1 上安装 Mojarra 2.1.21(由于 3rd 方不兼容而需要),如下所述:JSF how upgrade to Mojarra 2.1.21 in Netbeans7.1(只是sub jsf-api.jar 和 jsf-impl.jar 失败)

这对我的项目来说是一个很好的结果。没有 Stackoverflow 我该怎么办 :)

于 2013-05-28T05:18:28.790 回答
3

这个问题是我为什么不喜欢 JSF 的一个很好的例子:一旦你做任何不平凡的事情(例如 - 喘气 - 试图大规模重用代码),你就需要 JSF 内部知识。

JSF 表示具有组件树的视图。该树是由标签处理程序根据视图定义构建的,是有状态的,并且在用户离开视图之前一直存在。复合组件的包含由标记处理程序完成。c:if 也由标签处理程序实现。

在请求处理生命周期的每个阶段都会遍历组件树。然而,由各个组件决定是否(或多少次)他们的孩子被处理。这就是渲染属性的实现方式:在每个阶段,组件检查它是否被渲染,如果没有,则跳过对自身(及其子级)的处理。

JSF 视图范围保存在 UIViewRoot 内,它是组件树的根节点。因此,在处理标记处理程序时它不可用。这是它的许多缺点之一:-)

所以,你可以做什么?

  1. 您可以为活动树中的每个活动包含一个复合组件,但由于该包含发生在视图构建时,它不能按需发生。即使抛开相互递归的问题,如果用户希望看到特定的对话框,为每个子活动创建一个对话框也可能是浪费的。但是您当然可以使用 c:if 限制递归,您只需要将信息放在视图构建时可用的范围内。

  2. 或者,您可以在组件树中创建单个对话框,但通过将当前活动绑定到您更新其目标的 EL 表达式,让它在不同时间显示不同的活动。当然,这意味着一次只显示一个对话框。如果您需要堆叠子活动的对话框,您可以创建与树的深度一样多的对话框。

  3. 或者,您可以创建一个在请求处理生命周期的每个阶段递归处理自身的组件。这可以是您适应的现有组件(例如树组件),也可以是从头开始编写的组件(这并非微不足道,因为组件树是有状态的,并且必须为子组件的每次迭代保留和恢复状态)。

于 2013-05-22T04:46:55.540 回答