添加 f:ajax 到 compositecomponent 时 AjaxHandler.applyAttachedObject 中的 NPE

NPE in AjaxHandler.applyAttachedObject when adding f:ajax to compositecomponent

我围绕 primefaces 5.1 数据表创建了一个复合组件:

没有AJAX的组件(selectorServicio2.xhtml

<ui:component
  xmlns="http://www.w3.org/1999/xhtml"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:cc="http://java.sun.com/jsf/composite"
  xmlns:c="http://java.sun.com/jsp/jstl/core"
  xmlns:p="http://primefaces.org/ui"
  xmlns:pe="http://primefaces.org/ui/extensions">

  <cc:interface componentType="selectorServicio2">
    <cc:attribute name="servicios" required="true" type="java.util.Collection"
      shortDescription="Colección de servicios entre los que se buscarán los datos"/>
    <cc:attribute name="seleccionSimpleAttr" type="es.imasmallorca.selene.model.prestacion.Servicio"/>
    <cc:attribute name="seleccionMultipleAttr" type="java.util.List"/>
    <cc:attribute name="seleccionMultiple" required="false" type="java.lang.Boolean" default="false"/>
    <cc:attribute name="scrollHeight" required="false" type="java.lang.Integer"/>
  </cc:interface>

  <cc:implementation>
    <p:dataTable id="selectorServicio" widgetVar="#{cc.widgetId}"
      value="#{cc.attrs.servicios}" var="_servicio"
      scrollable="#{not empty cc.attrs.scrollHeight}"   scrollHeight="#{cc.attrs.scrollHeight}"
      selectionMode="#{cc.attrs.seleccionMultiple ? 'multiple' : 'single'}"
      selection="#{cc.attrs.seleccionMultiple ? cc.attrs.seleccionMultipleAttr : cc.attrs.seleccionSimpleAttr}" rowKey="#{_servicio.codigo}">

      <p:column>
        <h:outputText value="#{_servicio.entidad.nombre}"/>
      </p:column>

      <p:column>
        <h:outputText value="#{_servicio.tipoServicio.nombre}"/>
      </p:column>
    </p:dataTable>
  </cc:implementation>
</ui:component>

有后盾class (SelectorServicio2.java):

@FacesComponent("selectorServicio2")
public class SelectorServicio2 extends UIInput implements NamingContainer {

  private static final Logger log = Logger.getLogger(SelectorServicio2.class.getName());

  private static final String ID_SERVICIOS_SELECCIONADOS = "ServiciosSeleccionados";

  @Override
  public String getFamily() {
    return UINamingContainer.COMPONENT_FAMILY;
  }

  public void encodeBegin(FacesContext facesContext) throws IOException {
    @SuppressWarnings("unchecked")
    List<Servicio> servicios = (List<Servicio>) this.getAttributes().get("servicios");
    if (servicios == null) {
      servicios = new ArrayList<>();
    }
    Collections.sort(servicios, new ServicioPorNombreEntidadTipoServicio(Criterio.ENTIDAD_TIPOSERVICIO));
    getStateHelper().put("servicios", servicios);
    getStateHelper().put(ID_SERVICIOS_SELECCIONADOS, new ArrayList<Servicio>());

    super.encodeBegin(facesContext);
  }

  @Override
  public Object getValue() {
    ArrayList<Servicio> servicios = new ArrayList<>();
    Servicio servicio = (Servicio) this.getStateHelper().get("servicio");
    if (servicio != null) {
      servicios.add(servicio);
    }
    return servicios;
  }

  public String getWidgetId() {
    return "wdgSelectorServicio2_" + this.getClientId().replace(":", "_");
  }

  public void setServicioSeleccionado(Servicio servicioSeleccionado) {
    if (servicioSeleccionado == null) {
      this.getStateHelper().remove("servicio");
    } else {
      this.getStateHelper().put("servicio", servicioSeleccionado);
    }
  }

  public Servicio getServicioSeleccionado() {
    return (Servicio) this.getStateHelper().get("servicio");
  }
}

这按预期工作(text.xhtml):

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
  xmlns:h="http://xmlns.jcp.org/jsf/html"
  xmlns:f="http://xmlns.jcp.org/jsf/core"
  xmlns:p="http://primefaces.org/ui"
  xmlns:imas="http://java.sun.com/jsf/composite/components">

<h:head></h:head>
<h:body>
  <h:form>
    <imas:selectorServicio2 id="serviceSelector"
      servicios="#{testBean.services}"
      seleccionSimpleAttr="#{testBean.selectedService}"
      scrollHeight="110">
    </imas:selectorServicio2>
  </h:form>
</h:body>
</html>

现在,要添加 Ajax 支持,我这样做:

  1. <cc:clientBehavior name="servicioSeleccionado" event="rowSelect" targets="selectorServicio"/> 添加到 selectorServicio2.xhtmlcc:interface
  2. 使 SelectorServicio2 实现 ClientBehaviorHolder(奇怪的是,我不需要实现任何方法,但编译 class 没有问题)。
  3. 将ajax标签添加到test.xhtml:

    <imas:selectorServicio2 id="serviceSelector"
      servicios="#{testBean.services}"
      seleccionSimpleAttr="#{testBean.selectedService}"
      scrollHeight="110">
      <f:ajax event="servicioSeleccionado" listener="#{testBean.selectService}"/>
    </imas:selectorServicio2>
    
  4. selectService()方法添加到TestBean.java:

    public void selectService() {
      log.warning("SERVICIO SELECCIONADO !!!!");
    }
    

在这些更改之后,我得到一个 NullPointerException:

java.lang.NullPointerException at com.sun.faces.facelets.tag.jsf.core.AjaxHandler.applyAttachedObject(AjaxHandler.java:333) at com.sun.faces.facelets.tag.jsf.core.AjaxHandler.applyNested(AjaxHandler.java:258) at com.sun.faces.facelets.tag.jsf.core.AjaxHandler.apply(AjaxHandler.java:182) at javax.faces.view.facelets.DelegatingMetaTagHandler.applyNextHandler(DelegatingMetaTagHandler.java:137) at com.sun.faces.facelets.tag.jsf.CompositeComponentTagHandler.applyNextHandler(CompositeComponentTagHandler.java:183) at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.apply(ComponentTagHandlerDelegateImpl.java:203) at javax.faces.view.facelets.DelegatingMetaTagHandler.apply(DelegatingMetaTagHandler.java:120) at javax.faces.view.facelets.DelegatingMetaTagHandler.applyNextHandler(DelegatingMetaTagHandler.java:137) at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.apply(ComponentTagHandlerDelegateImpl.java:203) at javax.faces.view.facelets.DelegatingMetaTagHandler.apply(DelegatingMetaTagHandler.java:120) at javax.faces.view.facelets.DelegatingMetaTagHandler.applyNextHandler(DelegatingMetaTagHandler.java:137) at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.apply(ComponentTagHandlerDelegateImpl.java:203) at javax.faces.view.facelets.DelegatingMetaTagHandler.apply(DelegatingMetaTagHandler.java:120) at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95) at com.sun.faces.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:93) at com.sun.faces.facelets.compiler.EncodingHandler.apply(EncodingHandler.java:87) at com.sun.faces.facelets.impl.DefaultFacelet.apply(DefaultFacelet.java:161) at com.sun.faces.application.view.FaceletViewHandlingStrategy.buildView(FaceletViewHandlingStrategy.java:995) at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:99) at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101) at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:219)

我做错了什么?

我在 Java 7

上使用标准配置 (JSF 2.2) 的 Wildfly 8.1
java.lang.NullPointerException
    at com.sun.faces.facelets.tag.jsf.core.AjaxHandler.applyAttachedObject(AjaxHandler.java:333)

让我们检查一下 source of AjaxHandler#applyAttachedObject():

332            Collection<String> eventNames = bHolder.getEventNames();
333            if (!eventNames.contains(eventName)) {
334                throw new TagException(this.tag, 
335                    getUnsupportedEventMessage(eventName, eventNames, parent));
336            }

啊哈,getEventNames() 返回 null。该方法是在 UIComponentBase 上实现的,其 javadoc 表示如下:

This is a default implementation of ClientBehaviorHolder.getEventNames(). UIComponent does not implement the ClientBehaviorHolder interface, but provides default implementations for the methods defined by ClientBehaviorHolder to simplify subclass implementations. Subclasses that wish to support the ClientBehaviorHolder contract must declare that the subclass implements ClientBehaviorHolder, and must override this method to return a non-Empty Collection of the client event names that the component supports.

从技术上讲,您应该在组件中重写 getEventNames(),返回支持的事件名称的集合。

然而,你实际上并不需要它。 <cc:clientBehavior> 已经将其重定向到嵌套在复合材料中的 <p:dataTable> 组件上,该组件已经正确实现了该接口和方法。因此,您应该 删除 支持组件中的 ClientBehaviorHolder 接口。

此外,<f:ajax> 不适用于 PrimeFaces 特定事件,您应该改用 <p:ajax>。它将呈现一个脚本,该脚本依次调用 PrimeFaces 特定的 ajax 客户端 API.