这个问题与我之前关于initializing composite components的问题有关。假设我有一个自己开发的复合组件和一个@ViewScoped
托管bean,我希望托管bean的某些属性能够反映在组件中。属性值到组件的转换由复合属性完成。
这就是我现在所拥有的代码,包含@ManagedBean
,它定义了要在组件上呈现的随机行号。该组件在每一行中生成h:dataTable
h:commandButton
,按下该按钮只调用组件支持代码中的方法,执行回发到同一视图。
托管bean:
@ManagedBean
@ViewScoped
public class Bean implements Serializable {
private Integer rowNum;
public Integer getRowNum() {
return rowNum;
}
/**
* Initializes the rowNum property with a value between 1 and 9
*/
public void init() {
if (!FacesContext.getCurrentInstance().isPostback()) {
rowNum = new Random().nextInt(10) + 1;
System.out.println("Bean initialized");
}
}
public void setRowNum(Integer rowNum) {
this.rowNum = rowNum;
}
}
的index.xhtml:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:comp="http://java.sun.com/jsf/composite/comp">
<h:head />
<body>
<f:event type="preRenderView" listener="#{bean.init}" />
<h:form>
<comp:myTable itemNumber="#{bean.rowNum}" />
</h:form>
</body>
</html>
MyTable.java:
@FacesComponent("components.myTable")
public class MyTable extends UINamingContainer {
private List<String> values = new ArrayList<String>();
public void action() {
System.out.println("Called");
}
public List<String> getValues() {
return values;
}
/**
* Initializes the 'values' List based in 'itemNumber' attribute
*/
public void init() {
System.out.println("Component initialized");
Integer num = (Integer) getAttributes().get("itemNumber");
for (int i = 0; i < num; i++) {
values.add("item" + i);
}
}
}
myTable.xhtml:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:composite="http://java.sun.com/jsf/composite"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:body>
<composite:interface componentType="components.myTable">
<composite:attribute name="itemNumber"
type="java.lang.Integer" required="true" />
</composite:interface>
<composite:implementation>
<f:event type="preRenderView" listener="#{cc.init}" />
<h:dataTable value="#{cc.values}" var="value">
<h:column headerText="column">
#{value}
<h:commandButton value="Action" action="#{cc.action}" />
</h:column>
</h:dataTable>
</composite:implementation>
</h:body>
</html>
鉴于components themselves are stateless,要呈现的行号必须由Bean
实例确定,以便在发生回发时不会丢失其值。
@ManagedBean
和组件由init()
方法初始化,当preRenderView
事件发生时,该方法被调用。考虑到它必须保持视图状态,@ManagedBean
检查它实际上不是回发请求。然而,该组件是无状态的,不执行该检查,因为它在每个请求中丢失其值并且必须在所有请求中初始化。
问题是点击h:commandButton
中的任何一个会使空List
呈现。在调用init()
方法之前,似乎正在阅读它。但是,当执行 GET 请求时,情况不会发生。
避免该问题的一种方法是在getValues()
方法进行延迟初始化,在init()
方法中调用preRenderView
方法,而不是使用postAddToView
事件从视图中进行,尽管我认为它不是最优雅的方式。
使用preRenderView
代替@ManagedBean
事件来初始化组件既不起作用,因为它在{{1}}初始化之前被调用。
在Mojarra 2.1.27中测试 - 2.2.5 + Tomcat 7