I have a page with a very simple form which submits ajax requests targeted only at components in that same form. In the same page (but outside the form), there is also a ui:repeat
which iterates over an array returned from a request scoped managed bean (suppose a list of product categories). This bean has no properties bound in the form and is not accessed in any other way apart from the value
attribute of the ui:repeat
tag. I don't understand why JSF needs to recreate the request scoped bean on every ajax postback, just as if I asked to render this external ui:repeat
(which has nothing to do with the form) along with some component inside the form.
Is it a bug? Or is it an expected behavior? Of course I could annotate the bean as ViewScoped
but I don't see the reason to have categories stored in the view scope as they are completely static between postbacks.
Another solution/workaround I found is to render the ui:repeat
only in case of non-ajax requests:
<ul>
<ui:repeat value="#{someRequestScopedBean.categories}" var="category" rendered="#{not facesContext.partialViewContext.ajaxRequest}">
<li>#{category.name}</li>
</ui:repeat>
</ul>
but I don't know if this could cause problems and looks not very clear.
TEST CASE
index.xhtml:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<h:head>
<title>Facelet Title</title>
</h:head>
<h:body>
<h1>Test page</h1>
<p>Random jokes</p>
<ul>
<ui:repeat value="#{oneLiners.list}" var="oneliner">
<li>#{oneliner}</li>
</ui:repeat>
</ul>
<h:form>
<h:selectOneMenu value="#{backingBean.greeting}" hideNoSelectionOption="true">
<f:selectItem value="#{null}" itemLabel="Select a greeting..." noSelectionOption="true"/>
<f:selectItems value="#{backingBean.greetings}"/>
<f:ajax render="@this btn"/>
</h:selectOneMenu>
<h:commandButton id="btn" value="Say Hello!" disabled="#{empty backingBean.greeting}">
<f:ajax render="otxt"/>
</h:commandButton>
<h:outputText id="otxt" value="#{backingBean.greeting}, Maurizio!" style="display: #{empty backingBean.greeting ? 'none' : 'block'}"/>
</h:form>
</h:body>
</html>
Request scoped bean:
package testuirepajax;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
/**
*
* @author maurizio
*/
@ManagedBean
@RequestScoped
public class OneLiners {
private String[] list;
public OneLiners() {
System.out.println("testuirepajax.OneLiners.<init>()");
list = new String[] {
"Life is wonderful. Without it we'd all be dead.",
"Daddy, why doesn't this magnet pick up this floppy disk?",
"Daddy, what does FORMATTING DRIVE C mean?",
"Never forget: 2 + 2 = 5 for extremely large values of 2.",
"C:\\ is the root of all directories."
};
}
public String[] getList() {
System.out.println("testuirepajax.OneLiners.getList()");
return list;
}
}
Form backing bean:
package testuirepajax;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
/**
*
* @author maurizio
*/
@ManagedBean
@ViewScoped
public class BackingBean {
private String[] greetings;
private String greeting;
public BackingBean() {
System.out.println("testuirepajax.BackingBean.<init>()");
greetings = new String[] {
"Hello", "Hi", "Good morning", "Good evening", "Good night"
};
}
public String[] getGreetings() {
return greetings;
}
public void setGreeting(String greeting) {
this.greeting = greeting;
}
public String getGreeting() {
return greeting;
}
}
Check the output of your container. Using the Payara Server (which ships Mojarra 2.2.12), I see lines like these:
Informazioni: testuirepajax.OneLiners.<init>()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.<init>()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
when selecting elements from the menu or clicking the "Say Hello!" button.
I placed a breakpoint on getList()
method and inspected the call stack when it's "unnecessarily" hit during postback in order to learn about the who and the why:
Daemon Thread [http-nio-8088-exec-5] (Suspended (breakpoint at line 23 in OneLiners))
owns: NioChannel (id=83)
OneLiners.getList() line: 23
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 62
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43
Method.invoke(Object, Object...) line: 497
BeanELResolver.getValue(ELContext, Object, Object) line: 97
DemuxCompositeELResolver._getValue(int, ELResolver[], ELContext, Object, Object) line: 176
DemuxCompositeELResolver.getValue(ELContext, Object, Object) line: 203
AstValue.getValue(EvaluationContext) line: 169
ValueExpressionImpl.getValue(ELContext) line: 184
TagValueExpression.getValue(ELContext) line: 109
UIRepeat.getValue() line: 279
UIRepeat.getDataModel() line: 255
UIRepeat.visitTree(VisitContext, VisitCallback) line: 727
HtmlBody(UIComponent).visitTree(VisitContext, VisitCallback) line: 1700
UIViewRoot(UIComponent).visitTree(VisitContext, VisitCallback) line: 1700
PartialViewContextImpl.processComponents(UIComponent, PhaseId, Collection<String>, FacesContext) line: 403
PartialViewContextImpl.processPartial(PhaseId) line: 266
UIViewRoot.processDecodes(FacesContext) line: 927
ApplyRequestValuesPhase.execute(FacesContext) line: 78
ApplyRequestValuesPhase(Phase).doPhase(FacesContext, Lifecycle, ListIterator<PhaseListener>) line: 101
LifecycleImpl.execute(FacesContext) line: 198
FacesServlet.service(ServletRequest, ServletResponse) line: 658
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 291
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 206
WsFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 52
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 239
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 206
StandardWrapperValve.invoke(Request, Response) line: 212
StandardContextValve.invoke(Request, Response) line: 106
FormAuthenticator(AuthenticatorBase).invoke(Request, Response) line: 502
StandardHostValve.invoke(Request, Response) line: 141
ErrorReportValve.invoke(Request, Response) line: 79
AccessLogValve(AbstractAccessLogValve).invoke(Request, Response) line: 616
StandardEngineValve.invoke(Request, Response) line: 88
CoyoteAdapter.service(Request, Response) line: 521
Http11NioProcessor(AbstractHttp11Processor<S>).process(SocketWrapper<S>) line: 1096
Http11NioProtocol$Http11ConnectionHandler(AbstractProtocol$AbstractConnectionHandler<S,P>).process(SocketWrapper<S>, SocketStatus) line: 674
NioEndpoint$SocketProcessor.doRun() line: 1500
NioEndpoint$SocketProcessor.run() line: 1456
ThreadPoolExecutor(ThreadPoolExecutor).runWorker(ThreadPoolExecutor$Worker) line: 1142
ThreadPoolExecutor$Worker.run() line: 617
TaskThread$WrappingRunnable.run() line: 61
TaskThread(Thread).run() line: 745
The interesting lines are the ones above FacesServlet
. The class/method names already kind of speak for themselves.
It thus happened during apply request values phase, when the partial request needs to process the decode of components. The component tree is visited in order to find the components identified by client IDs specified in <f:ajax execute>
(which defaults to @this
). As the <ui:repeat>
is in the way before the component of interest, it's inspected first. A visitTree()
triggers full iteration because the client IDs of interest are only available during iteration.
When I moved <ui:repeat>
to below the <h:form>
, it isn't invoked anymore. All components of interest have already been found at that point.
This behavior is, unfortunately, "by design". Your work around is a good one. Better is to check #{not facesContext.postback}
instead as this also covers non-ajax postbacks.