my project is built using JBoss Seam 2.3, JSF 2.1 and richfaces. There's something I don't fully understand when re-rendering components using richfaces aj4 calls.
When some action is executed using a4j and the value of the render attribute points to some h:panelGroup id, methods in other panel are called too.
I prepared some code to demonstrate this.
BeanA.java. Simulates data access.
package test;
import java.util.Arrays;
import java.util.List;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.log.Log;
@Scope(ScopeType.CONVERSATION)
@Name("beanA")
public class BeanA {
@Logger
private Log log;
public List<Integer> list() {
log.info("beanA.list()");
// DB Query simulation
Integer[] result = {1,2,3};
return Arrays.asList(result);
}
}
BeanB.java. Simple clicks counter.
package test;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.log.Log;
@Scope(ScopeType.CONVERSATION)
@Name("beanB")
public class BeanB {
@Logger
private Log log;
private int counter = 0;
public void dumbAction() {
// do nothing
log.info("beanB.dumbAction()");
counter++;
}
public int getCounter() {
return counter;
}
public void setCounter(int counter) {
this.counter = counter;
}
}
test.xhtml
<f:view xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:a="http://richfaces.org/a4j"
xmlns:richext="http://java.sun.com/jsf/composite/richext"
xmlns:s="http://jboss.org/schema/seam/taglib" contentType="text/html"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<h:html>
<h:head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>TEST</title>
</h:head>
<h:body>
<h:panelGroup id="listPanel">
<ul>
<li>
<h:outputText value="a:repeat" />
</li>
<a:repeat value="#{beanA.list()}" var="i">
<li>
<h:outputText value="#{i}" />
</li>
</a:repeat>
</ul>
<!-- <ul> -->
<!-- <li> -->
<!-- <h:outputText value="ui:repeat" /> -->
<!-- </li> -->
<!-- <ui:repeat value="#{beanA.list()}" var="i"> -->
<!-- <li> -->
<!-- <h:outputText value="#{i}" /> -->
<!-- </li> -->
<!-- </ui:repeat> -->
<!-- </ul> -->
<!-- <ul> -->
<!-- <li> -->
<!-- <h:outputText value="c:foreach" /> -->
<!-- </li> -->
<!-- <c:forEach items="#{beanA.list()}" var="i"> -->
<!-- <li> -->
<!-- <h:outputText value="#{i}" /> -->
<!-- </li> -->
<!-- </c:forEach> -->
<!-- </ul> -->
</h:panelGroup>
<h:form>
<a:commandLink value="Dumb action" action="#{beanB.dumbAction()}"
execute="@this" render="timesDumbActionPanel" />
</h:form>
<h:panelGroup id="timesDumbActionPanel">
<h:outputText value="#{beanB.counter}" />
</h:panelGroup>
</h:body>
</h:html>
</f:view>
When I load the page /test.seam beanA.list() is called once. When I click "Dumb action" link the following is logged:
19:28:21,461 INFO [test.BeanA] (http--0.0.0.0-80-1) beanA.list()
19:28:21,461 INFO [test.BeanB] (http--0.0.0.0-80-1) beanB.dumbAction()
19:28:21,461 INFO [test.BeanA] (http--0.0.0.0-80-1) beanA.list()
beanA.list() is called twice and I don't understand why. When using ui:repeat instead of a:repeat beanA.list() is called like 11 times. I also tried to change scopes but the results were the same.
The behaviour I expect is just call beanB.dumbAction() and re-render the panel to refresh counter.
I do know getters are called multiple times, but does this apply to all methods too?
What I do to avoid this is just create a field for the list and modify the getter to initialize it if it's not. Something like this:
@Name("beanA")
public class BeanA {
@Logger
private Log log;
private List<Integer> list;
public void initList() {
log.info("beanA.list()");
// DB Query simulation
Integer[] result = { 1, 2, 3 };
setList(Arrays.asList(result));
}
public List<Integer> getList() {
if (list == null)
initList();
return list;
}
public void setList(List<Integer> list) {
this.list = list;
}
}
The getter is still called but not initList() which is just OK. This way I avoid executing queries more than necessary. The only problem is when data changes. I need to manually call initList() again and it's not enough with just re-rendering it's panel.
Maybe I'm missing something with JSF lifecycle or something, could you please give me some hint on this? Thank you!!
It's not like JSF knows that a specific method is a getter. If you're using in a place that asks for data, e.g. @value then it's a getter.
The reason you're seeing multiple calls to getters is because when JSF/RichFaces is looking for a certain element it has to go through all the elements before it. In your case in order to find
timesDumbActionPanelit has to go throughlistPaneland check inside thea4j:repeat.You can call
initList()from whatever method is changing the data.