前提非常简单:我有一个依赖嵌套复合组件(CC)进行渲染的页面布局,即一个JSF页面引用CC,CC本身引用另一个CC,其中包含对第三个CC的调用。 - 来源如下。
现在,当第三个CC想要使用ajax执行bean方法时,这将导致完全自包含的部分更新......没有任何反应。*
我已经广泛搜索了SO和其他地方,并调查了BalusC的有见地的帖子here的所有要点,但我已经空了。跟踪日志记录最终在应用,验证和呈现阶段发出以下消息,导致“空”响应:FINER [javax.enterprise.resource.webcontainer.jsf.context] ... JSF1098:未访问以下clientIds部分遍历后:fooForm:j_idt14:j_idt15:j_idt18。这是浪费处理器时间,可能是由于VDL页面中的错误。
*)这只发生在非常特殊的情况下(虽然这是我的用例的确切定义):
<cc:insertChildren />
插入“较低”CC的子节点。actions
。 (但即使在同一个调用中,也不一定只在同一个组件中。)所有这些都必须同时满足:如果我在层次结构中使用最里面的CC(或者包括对最终CC的调用的嵌套都在调用页面内),那么一切都有效。如果我使用facet,没问题。如果我删除或硬编码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>
Innermost 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>
外包装CC:(<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:部分遍历后,未访问以下clientId:form:level1:level3:按钮。这是浪费处理器时间,可能是由于VDL页面中的错误。
第一部分是正确的,这确实是技术问题,但是“这是浪费处理器时间并且可能是由于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?