我有一个比我将在此处描述的问题更大的问题,但由于难以解释和调试我的代码,我创建了一个更小(且愚蠢)的问题来重现相同的错误。
在我看来,我想打印一个字符串列表,这个列表是根据偏移量计算的。在我的 java 类中,我有一个巨大的静态字符串数组。为了计算我的字符串列表(将打印在页面中),我将静态数组中的值复制到我的列表中,从“偏移量”的值开始。
在我的页面中,我还有一个“增加偏移量”按钮,它通过 ajax 请求增加“偏移量”的值并更新结果(根据新偏移量重新计算列表)。
我的问题是:当我单击“增加偏移量”按钮时,执行服务器端代码,偏移量增加,但更新时出现问题:显示当前偏移量的文本使用“偏移量”的新值更新,但是字符串列表不会发生同样的情况,它会使用旧值进行更新,并且仅在我进行第二次请求时才考虑新值,在第二次请求中计算的值仅在第三次更新中考虑,使视图中的字符串列表总是延迟一个请求。
查看此图像(在新选项卡中将其打开以使其变大):
让我们看一些代码...
按钮:
<h:form>
<h:commandButton value="Increment offsets">
<f:ajax listener="#{test.incrementOffset()}" render=":stringList" />
</h:commandButton>
</h:form>
字符串列表:
<h:panelGroup id="stringList">
<h1>Offsets increment = #{test.offset}</h1>
<comp:stringPrinter offset="#{test.offset}" />
</h:panelGroup>
这就是我在标题中谈到的组件。页面不会计算或打印字符串列表。它将被计算并打印在一个复合组件中。
在查看复合组件的代码之前,这里是视图中使用的托管 bean“Test”:
@ManagedBean(name="test")
@ViewScoped
public class Test implements Serializable {
private int offset;
public int getOffset() {
return offset;
}
public void incrementOffset(){
offset++;
}
}
复合组件:正如我所说的,它只是接收“偏移量”作为参数并打印字符串列表。查看下面的代码:
<h:body>
<composite:interface componentType="stringPrinter">
<composite:attribute name="offset" type="java.lang.Integer" required="true" />
</composite:interface>
<composite:implementation>
<ul>
<ui:repeat value="#{cc.list}" var="string">
<li>#{string}</li>
</ui:repeat>
</ul>
</composite:implementation>
</h:body>
这就是字符串列表的计算方式:
@FacesComponent(value="stringPrinter")
public class StringPrinter extends UINamingContainer implements Serializable {
private ArrayList<String> list;
private static String[] LIPSUM = "...".split(" "); // "..." is not the actual string, the string is irrelevant (it has more than 400 words separated by spaces).
private static int ARRAY_SIZE = 12;
private void generateList(){
int offset = (Integer) getAttributes().get("offset");
int position = offset;
list = new ArrayList<String>();
for (int i = 0; i < ARRAY_SIZE; i++){
list.add(LIPSUM[position++ % LIPSUM.length]);
}
}
public ArrayList<String> getList() {
if (null == list) generateList();
return list;
}
}
}
如果我不使用组件,问题就不会发生。当列表在父视图中计算并且组件没有附加 java 类时,问题不会发生。但在我真正的问题中,我不需要打印字符串,我真的需要由复合组件处理的东西,因此必须有一个专门的类。
一些调试:
ui:repeat 标签可能是问题所在。如果我需要打印列表中的第一个字符串而不是打印列表(这需要 ui:repeat),那么所有内容都会在正确的时间正确更新。查看不使用 ui:repeats 的复合组件的代码:
<h:body>
<composite:interface componentType="stringPrinter">
<composite:attribute name="offset" type="java.lang.Integer" required="true" />
</composite:interface>
<composite:implementation>
<h:outputText value="#{cc.list.get(0)}" />
</composite:implementation>
</h:body>
上面的代码有效,但这不是我需要做的,我真的需要使用 ui:repeat。我还尝试了 c:forEach,结果相同。
为了进一步调试,我给自己提出了一个问题:“组件中的方法 getList() 是否在页面中的方法 incrementOffset() 执行后被调用?”。这是我得到的日志:
*** GET LIST CALLED!
*** GET LIST CALLED!
*** OFFSET INCREMENTED!
*** GET LIST CALLED!
*** GET LIST CALLED!
我使用 System.out.flush() 来防止消息被缓冲。似乎 getList() 在方法执行后被调用,我看不出有任何理由用旧值更新视图。无论如何,在 incrementOffset() 执行之前和之后两次调用它的事实有点奇怪。
此问题影响 Mojarra 2.1.7、2.1.22、2.1.23 和 2.2.0。我没有测试其他版本。
我将通过进一步的调试来更新这篇文章。
下载 Maven 项目并自己测试: zip 文件
提前感谢您的任何回答。