原始问题如下,但我已经提出了一个更简单的例子来证明这个问题,并认为它应该在顶部。
无论如何,在检查父元素是否实际呈现之前,似乎会处理ui:repeat
标记。要重新创建它,这里是facelet(minimalTest.xhtml):
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Test JSF <ui:repeat> inside <h:panelGroup rendered="false"></title>
</h:head>
<h:body>
<h:form>
<h1>Testing</h1>
<h:panelGroup rendered="false">
<span>#{minimalTestBean.alsoThrowsException}</span>
<ul>
<ui:repeat value="#{minimalTestBean.throwsException}" var="item">
<li>#{item}</li>
</ui:repeat>
</ul>
</h:panelGroup>
</h:form>
</h:body>
</html>
使用此bean(MinimalTestBean.java):
package com.lucastheisen.beans;
import java.io.Serializable;
import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
@ManagedBean
@ViewScoped
public class MinimalTestBean implements Serializable {
private static final long serialVersionUID = 9045030165653014015L;
public String getAlsoThrowsException() {
throw new RuntimeException( "rendered is false so this shouldnt get called either" );
}
public List<String> getThrowsException() {
throw new RuntimeException( "rendered is false so this shouldnt get called" );
}
}
从这个示例中,您可以看到包含h:panelGroup
的{{1}}被静态设置为ui:repeat
,我认为这意味着rendered=false
内的所有EL表达式都不是h:panelGroup
1}}会被执行。 EL表达式只调用抛出RuntimeException的getter。但是,ui:repeat
实际上是为其列表调用getter,从而导致异常,即使它不应该首先被渲染。如果你注释掉ui:repeat
元素,就不会像我期望的那样抛出异常(即使其他EL表达式仍然在h:panelGroup
中)。
在stackoverflow上阅读其他问题让我相信这可能与经常提到的chicken/egg问题有关,但我不确定为什么,也不知道如何处理它。我想将PARTIAL_STATE_SAVING设置为false可能有所帮助,但是希望避免内存影响。
----原始问题----
基本上,我有一个页面,有条件地使用<h:panelGroup rendered="#{modeXXX}">
围绕<ui:include src="pageXXX.xhtml" />
呈现部分(根据此answer)。问题是,如果其中一个pageXXX.xhtml内部有<ui:repeat>
,那么即使包含<h:panelGroup>
的{{1}}有rendered=false
,它似乎也会得到处理。这是一个问题,因为我的一些部分依赖于应该在它们之前访问的其他部分进行初始化。为什么包含的pageXXX.xhtml会被处理?
这是一个痛苦的错误,难以归结为一个小例子,但这是我可以构建的最小的案例,证明了这个问题。首先是一个基页:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Test JSF <ui:include></title>
</h:head>
<h:body>
<h:form>
<h1>#{testBean.title}</h1>
<h:panelGroup rendered="#{testBean.modeOne}">
<ui:include src="modeOne.xhtml" />
</h:panelGroup>
<h:panelGroup rendered="#{testBean.modeTwo}">
<ui:include src="modeTwo.xhtml" />
</h:panelGroup>
</h:form>
</h:body>
</html>
如您所见,此页面将根据testBean bean中的值有条件地包含modeOne页面或modeTwo页面。然后你有modeOne(默认):
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
<ui:composition>
<span>Okay, I'm ready. Take me to </span>
<h:commandLink action="#{testBean.setModeTwo}">mode two.</h:commandLink>
</ui:composition>
</html>
在我的真实应用程序中,哪个页面可以设置modeTwo所需的内容。设置完成后,此页面上的操作将引导您进入modeTwo:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
<ui:composition>
<div>Here is your list:</div>
<ui:repeat value="#{testBeanToo.list}" var="item">
<div>#{item}</div>
</ui:repeat>
</ui:composition>
</html>
modeTwo页面基本上呈现了ui中modeOne页面的详细信息:重复,因为实际信息在集合中。主托管bean(TestBean):
package test.lucastheisen.beans;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.ViewScoped;
@ManagedBean
@ViewScoped
public class TestBean implements Serializable {
private static final long serialVersionUID = 6542086191355916513L;
private Mode mode;
@ManagedProperty( value="#{testBeanToo}" )
private TestBeanToo testBeanToo;
public TestBean() {
System.out.println( "constructing TestBean" );
setModeOne();
}
public String getTitle() {
System.out.println( "\ttb.getTitle()" );
return mode.getTitle();
}
public boolean isModeOne() {
return mode == Mode.One;
}
public boolean isModeTwo() {
return mode == Mode.Two;
}
public void setModeOne() {
this.mode = Mode.One;
}
public void setModeTwo() {
testBeanToo.getReadyCauseHereICome();
this.mode = Mode.Two;
}
public void setTestBeanToo( TestBeanToo testBeanToo ) {
this.testBeanToo = testBeanToo;
}
private enum Mode {
One("Mode One"),
Two("Mode Two");
private String title;
private Mode( String title ) {
this.title = title;
}
public String getTitle() {
return title;
}
}
}
是所有主要数据的bean,还是TestBeanToo bean的详细信息:
package test.lucastheisen.beans;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
@ManagedBean
@ViewScoped
public class TestBeanToo implements Serializable {
private static final long serialVersionUID = 6542086191355916513L;
private ObjectWithList objectWithList = null;
public TestBeanToo() {
System.out.println( "constructing TestBeanToo" );
}
public String getTitle() {
System.out.println( "\ttb2.getTitle()" );
return "Test Too";
}
public List<String> getList() {
System.out.println( "\ttb2.getList()" );
return objectWithList.getList();
}
public void getReadyCauseHereICome() {
System.out.println( "\ttb2.getList()" );
objectWithList = new ObjectWithList();
}
public class ObjectWithList {
private List<String> list;
public ObjectWithList() {
list = new ArrayList<String>();
list.add( "List item 1" );
list.add( "List item 2" );
}
public List<String> getList() {
return list;
}
}
}