很抱歉没有在专门的测试用例中抽象出这个问题,我希望一个真实项目的例子足够简单来描述这个问题。
我有一个 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,如果:
弹出的 p:dialog 专家系统摘要显示正在检查的元素的状态图标指示器。
我包括附加的元素视图链接以及状态指示器(它们本身会启动 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=".."
> </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)如果仅用于“渲染”,也无济于事,