当组件在 JSTL forEach 循环中时,以编程方式使用值表达式设置属性

Programatically Set Attribute with a Value Expression when component is within JSTL forEach loop

第一次提问,请多多包涵。 我有一个扩展 UIInput(JSF 2.2,Mojarra)的纯 Java 自定义组件,我是这样使用它的:

<c:forEach items="#{bean.items}" var="item">
    <my:component item="#{item}" />
</c:forEach>

我试图避免在 .xhtml 文件的标签上不必要地指定 'value'、'valueChangeListener' 和 'validator' 属性。

在我的自定义组件中,我重写了 setValueExpression 方法,如下所示:

@Override
public void setValueExpression(String name, ValueExpression expression) {
    super.setValueExpression(name, expression);
    if ("item".equals(name)) {
        this.setValue(Components.createValueExpression("#{item.myValue}", MyValue.class));
        this.addValueChangeListener(new MethodExpressionValueChangeListener(Components.createVoidMethodExpression("#{item.myValueChanged}", ValueChangeEvent.class)));
        this.addValidator(new MethodExpressionValidator(Components.createVoidMethodExpression("#{item.validateMyValue}", FacesContext.class, UIComponent.class, Object.class)));
    }
}

我在那里使用 OmniFaces 及其组件实用程序来减少样板代码。

当需要对这三个中的任何一个采取行动时(例如,在提交时验证),结果是:

javax.faces.FacesException: javax.el.PropertyNotFoundException: Target Unreachable, identifier 'item' resolved to null

我很确定我知道为什么,我只是不知道该怎么办。

我相信当我试图以编程方式设置的三个表达式被解析时,它试图通过名称在某个范围内找到一个 bean,'item' 但是它不存在,因为 'item' 是 JSTL forEach 循环中的一个时间点变量。

我认为 Weld 对项目本身使用了一种特殊的延迟表达式(当我调试 setValueExpression 方法时我可以看到)它知道或以其他方式引用了那个时间点变量,但我'当我设置这三个表达式时,我没有做同样的事情,因此稍后需要解决它们时无法处理。

我确定有一种方法可以将其连接在一起,只是我没有看到而已。

此外,我知道我可以像这样将三个属性放在 .xhtml 中的标签上:

<my:component item="#{item}" value="#{item.myValue}" valueChangeListener="#{item.myValueChanged}" validator="#{item.validateMyValue}" />

然后他们会得到他们的特殊延迟表达式,就像项目本身所做的那样(实际上一切都按预期的方式工作)但我宁愿不这样做 - 这是我必须重复很多次的东西,看起来就像应该有一种方法可以做我上面正在尝试的事情。

我相信我找到了答案。

我一直在调试器中看到的内容一直困扰着我,我只是不确定如何手动连接相同的场景,然后我在最底部找到了一个带有以下代码片段的 Stack Overflow post :

VariableMapper varMapper = new DefaultVariableMapper();
varMapper.setVariable(mappingName, component.getValueExpression(mappedAttributeName));
return new ValueExpressionImpl(expression, null, null, varMapper, expectedType);

这足以为我指出正确的方向,即在 VariableMapper 实例中重新使用 item 本身的传入值表达式,该实例又用于创建三个 value/method 表达式,因此它们现在都具有句柄 & 可以稍后解决 'item',到时候:

@Override
public void setValueExpression(String name, ValueExpression expression) {
    super.setValueExpression(name, expression);
    if ("item".equals(name)) {

        VariableMapper varMapper = new DefaultVariableMapper();
        varMapper.setVariable("item", expression);

        ValueExpressionImpl valExprImpl = new ValueExpressionImpl("#{item.myValue}", null, null, varMapper, MyValue.class);
        super.setValueExpression("value", valExprImpl);

        MethodExpressionImpl meExprImpl = new MethodExpressionImpl("#{item.myValueChanged}", null, null, varMapper, Void.class, new Class<?>[] {ValueChangeEvent.class});
        MethodExpressionValueChangeListener mevcl = new MethodExpressionValueChangeListener(meExprImpl);
        this.addValueChangeListener(mevcl);

        meExprImpl = new MethodExpressionImpl("#{item.validateMyValue}", null, null, varMapper, Void.class, new Class<?>[] {FacesContext.class, UIComponent.class, Object.class});
        MethodExpressionValidator mev = new MethodExpressionValidator(meExprImpl);
        this.addValidator(mev);

    }
}

这似乎成功了(现在看起来很简单...)。