JSF复合组件目标的行动失败内c:forEach标记

问题描述:

我们使用commandButtons一个c:forEach标签,其中按钮的action接收forEachvar属性作为这样的参数内:JSF复合组件目标的行动失败内c:forEach标记

<c:forEach var="myItem" items="#{myModel}"> 
    <h:commandButton 
     action="#{myController.process(myItem)}" 
     value="#{myItem.name}" /> 
</c:forEach> 

这一切正常。如果我们将commandButton包装在复合组件中,但它不再起作用:控制器被调用,但参数始终为null。 下面是一个c:forEach标签的示例,其中包含一个按钮和包含按钮的复合组件。第一个工作,第二个不工作。

<c:forEach var="myItem" items="#{myModel}"> 
    <h:commandButton 
     action="#{myController.process(myItem)}" 
     value="#{myItem.name}" /> 
    <my:mybutton 
     action="#{myController.process(myItem)}" 
     value="#{myItem.name}" /> 
</c:forEach> 

具有以下my:mybutton实现:

<composite:interface> 
    <composite:attribute name="action" required="true" targets="button" /> 
    <composite:attribute name="value" required="true" /> 
</composite:interface> 

<composite:implementation> 
    <h:commandButton id="button" 
     value="#{cc.attrs.value}"> 
    </h:commandButton> 
</composite:implementation> 

请注意,按钮的value属性,这也势必c:ForEachvar,工作得很好。通过复合组件的targets机制传播的只是action,它不会被正确评估。 动物可以请解释,为什么会发生这种情况,以及如何解决这个问题?

我们在mojarra 2.2.8,el-api 2.2.5,tomcat 8.0上。

+0

我无法重现与Mojarra 2.2.14和Tomcat 8.5.16的问题。我只是没有在项目中手动包含EL,因为Tomcat已经有了自己的EL版本。 – BalusC

+0

当给定的c:forEach代码片段又被封装在另一个复合组件中时,我再现了你的问题。 – BalusC

这是由Mojarra中的错误引起的,也可能是JSF规范中关于复合组件重定向方法表达式的疏忽。

解决方法是在ViewDeclarationLanguage以下。

public class FaceletViewHandlingStrategyPatch extends ViewDeclarationLanguageFactory { 

    private ViewDeclarationLanguageFactory wrapped; 

    public FaceletViewHandlingStrategyPatch(ViewDeclarationLanguageFactory wrapped) { 
     this.wrapped = wrapped; 
    } 

    @Override 
    public ViewDeclarationLanguage getViewDeclarationLanguage(String viewId) { 
     return new FaceletViewHandlingStrategyWithRetargetMethodExpressionsPatch(getWrapped().getViewDeclarationLanguage(viewId)); 
    } 

    @Override 
    public ViewDeclarationLanguageFactory getWrapped() { 
     return wrapped; 
    } 

    private class FaceletViewHandlingStrategyWithRetargetMethodExpressionsPatch extends ViewDeclarationLanguageWrapper { 

     private ViewDeclarationLanguage wrapped; 

     public FaceletViewHandlingStrategyWithRetargetMethodExpressionsPatch(ViewDeclarationLanguage wrapped) { 
      this.wrapped = wrapped; 
     } 

     @Override 
     public void retargetMethodExpressions(FacesContext context, UIComponent topLevelComponent) { 
      super.retargetMethodExpressions(new FacesContextWithFaceletContextAsELContext(context), topLevelComponent); 
     } 

     @Override 
     public ViewDeclarationLanguage getWrapped() { 
      return wrapped; 
     } 
    } 

    private class FacesContextWithFaceletContextAsELContext extends FacesContextWrapper { 

     private FacesContext wrapped; 

     public FacesContextWithFaceletContextAsELContext(FacesContext wrapped) { 
      this.wrapped = wrapped; 
     } 

     @Override 
     public ELContext getELContext() { 
      boolean isViewBuildTime = TRUE.equals(getWrapped().getAttributes().get(IS_BUILDING_INITIAL_STATE)); 
      FaceletContext faceletContext = (FaceletContext) getWrapped().getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY); 
      return (isViewBuildTime && faceletContext != null) ? faceletContext : super.getELContext(); 
     } 

     @Override 
     public FacesContext getWrapped() { 
      return wrapped; 
     } 
    } 
} 

faces-config.xml如下安装它:

<factory> 
    <view-declaration-language-factory>com.example.FaceletViewHandlingStrategyPatch</view-declaration-language-factory> 
</factory> 

我怎么确定下来的呢?

我们已经确认问题在于,当在复合组件中调用操作时,方法表达式参数变为null,而操作本身在另一个组合组件中声明。

<h:form> 
    <my:forEachComposite items="#{['one', 'two', 'three']}" /> 
</h:form> 

<cc:interface> 
    <cc:attribute name="items" required="true" /> 
</cc:interface> 
<cc:implementation> 
    <c:forEach items="#{cc.attrs.items}" var="item"> 
     <h:commandButton id="regularButton" value="regular button" action="#{bean.action(item)}" /> 
     <my:buttonComposite value="cc button" action="#{bean.action(item)}" /> 
    </c:forEach> 
</cc:implementation> 

<cc:interface> 
    <cc:attribute name="action" required="true" targets="compositeButton" /> 
    <cc:actionSource name=""></cc:actionSource> 
    <cc:attribute name="value" required="true" /> 
</cc:interface> 
<cc:implementation> 
    <h:commandButton id="compositeButton" value="#{cc.attrs.value}" /> 
</cc:implementation> 

我所做的就是定位谁负责背后#{bean.action(item)}创建MethodExpression实例的代码的第一件事。我知道它通常是通过ExpressionFactory#createMethodExpression()创建的。我也知道所有EL上下文变量通常通过ELContext#getVariableMapper()提供。所以我在createMethodExpression()中放置了一个调试断点。

enter image description here 在调用栈,我们可以检查ELContext#getVariableMapper(),也谁负责创建MethodExpression。在又一个复合组件嵌套一个常规命令按钮和一个复合命令按钮测试页,我们可以看到在ELContext以下区别:

常规按钮: enter image description here

我们可以看到,经常按钮使用DefaultFaceletContext作为ELContext,并且VariableMapper包含右侧item变量。

复合键:

enter image description here 我们可以看出,复合按钮使用标准ELContextImplELContext,而且VariableMapper不包含正确item变量。因此,我们需要在调用堆栈中返回一些步骤,以查看此标准ELContextImpl的来源以及为什么使用它而不是DefaultFaceletContext

enter image description here

一旦发现创建特定ELContext实现中,我们可以发现,它是从FacesContext#getElContext()获得。但是这并不代表复合组件的EL背景!这由当前的FaceletContext表示。所以我们需要再回头去找出为什么FaceletContext没有被传递。

enter image description here

我们可以在这里看到CompositeComponentTagHandler#applyNextHander()没有经过FaceletContextFacesContext来代替。这是JSF规范中可能忽略的确切部分。 ViewDeclarationLanguage#retargetMethodExpressions()本应要求提供另一个参数,代表涉及的实际ELContext

但它是什么。我们现在无法立即改变规范。最好我们能做的就是向他们报告一个问题。

上面所示的FaceletViewHandlingStrategyPatch作品最终重写FacesContext#getELContext()如下如下:

@Override 
public ELContext getELContext() { 
    boolean isViewBuildTime = TRUE.equals(getWrapped().getAttributes().get(IS_BUILDING_INITIAL_STATE)); 
    FaceletContext faceletContext = (FaceletContext) getWrapped().getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY); 
    return (isViewBuildTime && faceletContext != null) ? faceletContext : super.getELContext(); 
} 

你看,它会检查,如果JSF目前正在建设的看法,如果有一个FaceletContext存在。如果是,则返回FaceletContext而不是标准ELContext执行(注意FaceletContext just extends ELContext)。这样,MethodExpression将创建右边ELContext举行权利VariableMapper

+0

情况与我们的情况并不完全相同:我们没有在复合组件中使用'',而是在使用''嵌入视图中的facelet中。这与您的设置不完全相同,但问题似乎具有相同的根本原因。我也可以在'ExpressionFactory#createMethodExpression()'中看到一个空的ELContext,你的修补程序也适用于我们。 –