JSF 拒绝处理深度嵌套的复合组件。不,真的是 : "JSF1098: [...] This is a waste of processor time [...]"

标签 jsf composite-component tree-traversal clientid

前提非常简单:我有一个依赖嵌套复合组件 (CC) 进行渲染的页面布局,即一个 JSF 页面引用一个 CC,该 CC 本身引用另一个 CC,后者包含对第三个 CC 的调用. - 来源如下。

现在,当第三个 CC 想要使用 ajax 执行一个 bean 方法时,这将导致完全独立的部分更新......没有任何反应。*

Nested JSF CCs: Black: CC that executes a call from attributes. Red: Outer CC that passes its children to blue. Blue: Intermediate that inserts children for red.

我已经广泛搜索了 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 , level3button并试图找到它作为给定组件的直接子组件。

为了让它运行,在 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/

相关文章:

javascript - 如何使用 JSF 复合组件使页面上的 id 唯一?

python - 在具有循环节点的复杂数据结构中寻找最短路径

c++ - C++ 中的 ZigZag 二叉树遍历

java - Jboss 7 Jaas模块,成功登录后角色被覆盖

java - JSF 2.0 - 即使第一个 validator 失败也会调用第二个 validator

jsf-2 - 为什么复合组件的 “rendered”属性会引发IllegalArgumentException?

jakarta-ee - 如何访问支持 UIComponent 中的复合组件属性值?

javascript - 函数如何找到最初调用该函数的 anchor 的父元素?

jsf - Omnifaces CombinedResourceHandler——有没有办法抑制某些资源?

ajax - h :commandLink action and f:ajax listener的调用顺序