前提非常简单:我有一个依赖嵌套复合组件 (CC) 进行渲染的页面布局,即一个 JSF 页面引用一个 CC,该 CC 本身引用另一个 CC,后者包含对第三个 CC 的调用. - 来源如下。
现在,当第三个 CC 想要使用 ajax 执行一个 bean 方法时,这将导致完全独立的部分更新......没有任何反应。*
我已经广泛搜索了 SO 和其他地方,并调查了 BalusC 有见地的帖子的所有要点 here ,但我已经空了。跟踪日志记录最终在应用、验证和呈现阶段出现以下消息,导致“空”响应:FINER [javax.enterprise.resource.webcontainer.jsf.context] ... JSF1098: The following clientIds部分遍历后未访问: fooForm:j_idt14:j_idt15:j_idt18 。这是对处理器时间的浪费,可能是由于 VDL 页面中的错误。
*) 这只会在非常特殊的情况下发生(不过,这是我的用例的确切定义):
- 深层嵌套的 CC,至少两个父级别。
- 嵌套必须是隐式的,即不同的 CC 调用另一个,而不仅仅是直接嵌套在调用页面内的标签。
- “高级”CC 使用
<cc:insertChildren />
传递插入“低级”CC 的 child . - 执行 ajax 调用和部分更新的 CC 包含
actions
从 CC 的属性或 clientId 动态创建。 (但甚至不一定在同一个调用中,只是在同一个组件内。)
必须同时满足所有条件:如果我使用层次结构中更高层的最内层 CC(或者包括对最终 CC 的调用在内的嵌套都在调用页面内),一切正常。如果我使用构面,没问题。如果我删除或硬编码 action
参数,一切正常。
(目前在 EE6、EAP 6.4 上测试,但在 EAP 7.0 上运行的 EE7 项目中是相同的)
调用页面:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:my="http://java.sun.com/jsf/composite/components/nestedcomponents">
<h:head>
<title>Nested composite component test</title>
</h:head>
<h:body>
<h:form id="fooForm">
<h2>Works</h2>
<my:randomString saveBean="#{util}" saveAction="doSomething" />
<h2>Doesn't</h2>
<my:containerInsertingAnotherUsingInsertChildren>
<my:randomString saveBean="#{util}" saveAction="doSomething" />
</my:containerInsertingAnotherUsingInsertChildren>
</h:form>
</h:body>
</html>
最里面的 CC:(<my:randomString>
,黑框;#{util}
是一个具有单行虚拟方法的请求范围的 bean)
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:cc="http://java.sun.com/jsf/composite"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<cc:interface>
<cc:attribute name="someValue" />
<!--cc:attribute name="someAction" method-signature="void action()" /-->
<!--cc:attribute name="someAction" targets="btn" targetAttributeName="action" /-->
<cc:attribute name="saveBean" />
<cc:attribute name="saveAction" />
</cc:interface>
<cc:implementation>
<h:panelGroup layout="block" id="box" style="border: 1px solid black; margin: 3px; padding: 3px;">
<h:outputText value="#{cc.attrs.id} / #{cc.clientId} / #{util.getRandomString()} " />
<h:commandLink id="btn" value="save!" action="#{cc.attrs.saveBean[cc.attrs.saveAction]}" >
<f:ajax render="box" immediate="true" />
</h:commandLink>
</h:panelGroup>
</cc:implementation>
</html>
外部,包装抄送:(<my:containerInsertingAnotherUsingInsertChildren>
,红框)
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:cc="http://java.sun.com/jsf/composite"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:my="http://java.sun.com/jsf/composite/components/nestedcomponents">
<cc:interface>
</cc:interface>
<cc:implementation>
<h:panelGroup layout="block" style="border: 1px solid red; margin: 3px; padding: 3px;">
<my:containerUsingInsertChildren>
<cc:insertChildren />
</my:containerUsingInsertChildren>
</h:panelGroup>
</cc:implementation>
</html>
中级 CC:(<my:containerUsingInsertChildren>
,蓝框)
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:cc="http://java.sun.com/jsf/composite"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui">
<cc:interface>
</cc:interface>
<cc:implementation>
<h:panelGroup layout="block" style="border: 1px solid blue; margin: 3px; padding: 3px;">
<cc:insertChildren />
</h:panelGroup>
</cc:implementation>
</html>
如我所写,硬编码调用按预期工作并更新小附加框。只要 bean 方法涉及 CC 的参数(属性),并且 CC 在层次结构中的位置足够深,它们就会被跳过。
我不知所措,欢迎提供解决方案或解决方法。
最佳答案
这是由 Mojarra 中的一个错误引起的,该错误与生成通过 <cc:insertChildren>
嵌套的复合组件的客户端 ID 有关。 .如果您为复合组件分配固定 ID,如下所示:
<h:form id="form">
<my:level1 id="level1">
<my:level3 id="level3" beanInstance="#{bean}" methodName="action" />
</my:level1>
</h:form>
据此level1.xhtml
实现为:
<cc:implementation>
<my:level2 id="level2">
<cc:insertChildren />
</my:level2>
</cc:implementation>
和level2.xhtml
作为:
<cc:implementation>
<cc:insertChildren />
</cc:implementation>
和level3.xhtml
作为:
<cc:implementation>
<h:commandButton id="button" value="Submit #{component.clientId}"
action="#{cc.attrs.beanInstance[cc.attrs.methodName]}">
<f:ajax />
</h:commandButton>
</cc:implementation>
然后你会注意到 #{component.clientId}
在提交按钮中说 form:level1:level3:button
而不是 form:level1:level2:level3:button
按照预期(另请参阅此相关问题的答案 How to find out client ID of component for ajax update/render? Cannot find component with expression "foo" referenced from "bar" )。
这是找到树遍历错误的线索。您收到的日志消息实际上具有误导性。
JSF1098: The following clientIds were not visited after a partial traversal: form:level1:level3:button. This is a waste of processor time and could be due to an error in the VDL page.
第一部分是正确的,这确实是技术问题,但是关于潜在原因的假设如“这是处理器时间的浪费,可能是由于 VDL 页面中的错误” 不正确。理论上只有当访问导致堆栈溢出错误时才会发生,这种错误只发生在大约 1000 级深度。这远不是这里的情况。
回到错误客户端 ID 问题的根本原因,不幸的是,如果不修复核心 JSF 实现本身(我将其报告为 issue 4339),解决这个问题并非易事。但是,您可以通过提供自定义访问上下文来解决它,该上下文提供要访问的正确子树 ID。在这里:
public class PartialVisitContextPatch extends VisitContextWrapper {
private final VisitContext wrapped;
private final Pattern separatorCharPattern;
public PartialVisitContextPatch(VisitContext wrapped) {
this.wrapped = wrapped;
char separatorChar = UINamingContainer.getSeparatorChar(FacesContext.getCurrentInstance());
separatorCharPattern = Pattern.compile(Pattern.quote(Character.toString(separatorChar)));
}
@Override
public VisitContext getWrapped() {
return wrapped;
}
@Override
public Collection<String> getSubtreeIdsToVisit(UIComponent component) {
Collection<String> subtreeIdsToVisit = super.getSubtreeIdsToVisit(component);
if (subtreeIdsToVisit != VisitContext.ALL_IDS) {
FacesContext context = getFacesContext();
Map<String, Set<String>> cachedSubtreeIdsToVisit = (Map<String, Set<String>>) context.getAttributes()
.computeIfAbsent(PartialVisitContextPatch.class.getName(), k -> new HashMap<String, Set<String>>());
return cachedSubtreeIdsToVisit.computeIfAbsent(component.getClientId(context), k ->
getIdsToVisit().stream()
.flatMap(id -> Arrays.stream(separatorCharPattern.split(id)))
.map(childId -> component.findComponent(childId))
.filter(Objects::nonNull)
.map(child -> child.getClientId(context))
.collect(Collectors.toSet())
);
}
return subtreeIdsToVisit;
}
public static class Factory extends VisitContextFactory {
private final VisitContextFactory wrapped;
public Factory(VisitContextFactory wrapped) {
this.wrapped = wrapped;
}
@Override
public VisitContextFactory getWrapped() {
return wrapped;
}
@Override
public VisitContext getVisitContext(FacesContext context, Collection<String> ids, Set<VisitHint> hints) {
return new PartialVisitContextPatch(getWrapped().getVisitContext(context, ids, hints));
}
}
}
默认情况下,当Mojarra来到<my:level2>
在树遍历期间调用 getSubtreeIdsToVisit()
,它会得到一个空集,因为组件 ID 字符串 level2
客户端 ID 字符串中不存在 form:level1:level3:button
.我们需要覆盖和操作 getSubtreeIdsToVisit()
以这种方式“正确”返回form:level1:level3:button
什么时候<my:level2>
正在传入。这可以通过将客户端 ID 分解为多个部分来完成 form
, level1
, level3
和 button
并试图找到它作为给定组件的直接子组件。
为了让它运行,在 faces-config.xml
中如下注册它:
<factory>
<visit-context-factory>com.example.PartialVisitContextPatch$Factory</visit-context-factory>
</factory>
也就是说,请确保您没有为了模板而滥用复合组件。最好改用标签文件。另见 When to use <ui:include>, tag files, composite components and/or custom components?
关于JSF 拒绝处理深度嵌套的复合组件。不,真的是 : "JSF1098: [...] This is a waste of processor time [...]",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48857301/