【问题标题】:JSF Composite Component target actions fail within the c:forEach TagJSF 复合组件目标操作在 c:forEach 标记内失败
【发布时间】:2017-11-21 19:35:07
【问题描述】:

我们在c:forEach 标记内使用commandButtons,其中按钮的action 接收forEach var 属性作为这样的参数:

<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:ForEach var,可以正常工作。只有通过复合组件的targets 机制传播的action 没有得到正确评估。 anonymone 可以解释一下,为什么会发生这种情况以及如何解决这个问题?

我们正在使用 mojarra 2.2.8、el-api 2.2.5、tomcat 8.0。

【问题讨论】:

  • 我无法重现您使用 Mojarra 2.2.14 和 Tomcat 8.5.16 的问题。我只是没有在项目中手动包含 EL,因为 Tomcat 已经有自己的 EL 版本。
  • 当给定的 c:forEach sn-p 依次包含在另一个复合组件中时,我重现了您的问题。

标签: jsf jstl el composite-component mojarra


【解决方案1】:

这是由 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()中放了一个调试断点。

​​​ 在调用堆栈中,我们可以检查ELContext#getVariableMapper() 以及谁负责创建MethodExpression。在一个复合组件依次嵌套一个常规命令按钮和一个复合命令按钮的测试页面中,我们可以在ELContext 中看到以下差异:

常规按钮: ​​​

我们可以看到常规按钮使用DefaultFaceletContext 作为ELContext,并且VariableMapper 包含正确的item 变量。

复合按钮:

​​​​ 我们可以看到复合按钮使用标准的ELContextImpl 作为ELContext,并且VariableMapper 不包含正确的item 变量。所以我们需要在调用堆栈中返回一些步骤,看看这个标准 ELContextImpl 的来源以及为什么使用它而不是 DefaultFaceletContext

一旦找到特定ELContext 实现的创建位置,我们可以发现它是从FacesContext#getElContext() 获得的。但这并不代表复合组件的 EL 上下文!这由当前的FaceletContext 表示。所以我们需要多退一步来弄清楚为什么FaceletContext没有被传递下去。

我们可以在这里看到CompositeComponentTagHandler#applyNextHander() 没有通过FaceletContext,而是通过FacesContext。这正是 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

【讨论】:

  • 情况与我们的情况不太一样:我们没有在复合组件中使用&lt;c:forEach&gt;,而是在使用&lt;ui:include&gt; 嵌入视图中的facelet 中。这与您的设置不太一样,但问题似乎具有相同的根本原因。我还可以在 ExpressionFactory#createMethodExpression() 中看到一个空的 ELContext,您的修复也适用于我们。
猜你喜欢
  • 2014-02-04
  • 1970-01-01
  • 2014-07-23
  • 1970-01-01
  • 1970-01-01
  • 2011-05-04
  • 2011-12-16
  • 2013-08-16
  • 2013-12-01
相关资源
最近更新 更多