对于没有在专用测试用例中抽象这个问题表示歉意,我希望来自真实项目的示例足够简单来描述问题。
我有一个 JavaEE/JPA2/JSF Web 应用程序,其中每个 @Entity 元素(或子类)都有一个模板化 view.xhtml 页面和一个标准链接生成器复合组件 util:view_link.xhtml,以数据库 ID 作为参数作为 GET 调用。每个视图页面的一部分(仅)代表专家系统摘要;该部分可以抽象为复合组件,以包含在视图页面或其他地方。
我引入了一个 Primefaces p:dialog 模式弹出窗口,用于在单击视图链接旁边显示的小状态图标时显示专家系统摘要部分(以及任何其他诊断)。如果让状态图标用 x 表示,它看起来像这样:
x 按 ID 链接到元素
单击“Link_to_Element_by_ID”,将显示完整视图页面。
单击“x”图标(专家系统测试失败指示器),会弹出 p:dialog,其中包含专家系统摘要(仅)。
因此,视图页面的专家系统部分作为复合组件共享。
但如果出现以下情况,这可能会导致递归和 Stackoverflow:
弹出的 p:dialog 专家系统摘要显示正在检查的元素的状态图标指示器。
我添加了其他元素视图链接以及状态指示器(它们本身会启动一个 p:dialog 以获取专家系统摘要)。
我尝试过使用递归阻止属性“preventRecursionOnDialog”来使用渲染测试,但它失败了,显然是因为递归是在构建阶段发生的。
问:如何使用测试变量阻止可能的递归?
另外,我尝试过 c:if 测试而不是 JSF“渲染”测试,但似乎测试的变量在 @ViewScoped 下不可用。
例如,对于 Activity 元素,其中 util_primefaces:dialog_summary 只是 p: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'}"
/>
(cf. EL表达式语言中的instanceof检查,并注意基于类字符串的类型测试解决方案不是很灵活,它不适用于子类或接口测试。)
同样,尝试使用“渲染”来阻止调用是不够的,似乎类型测试在构建阶段就已经失败了。递归问题的解决方案也可以解决这个问题。甚至JSF2中(终于)引入了instanceof(请在这里投票http://java.net/jira/browse/JSP_SPEC_PUBLIC-113) 如果仅用于“渲染”,则不会有帮助,