Why is BackingBean method called multiple times when requesting facelet? [duplicate]
Asked Answered
E

2

7

I'm working and learning about JSF + Facelets these days. I have a BackingBean and a Facelet xHTML page. When I request the facelet-page (only one time) the backing-bean-method is called multiple times.

What could be the reason for this?

I can't see anything special. Thanks in advance.

Here is the facelet:

<?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:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Insert title here</title>
</head>
<body>
<ui:composition template="index.xhtml">
    <ui:define name="content">
        <h:form>Name: <h:inputText id="nameFilterPattern" value="#{kundenBackingBean.nameFilterPattern}" /><h:commandButton value="Suchen"/></h:form>
        <h:dataTable var="kunde" value="#{kundenBackingBean.kunden}" rowClasses="rowHighlight, rowOrdinary">
            <h:column> 
                <f:facet name="header">
                    <h:outputText value="Kundennr" />
                </f:facet>
                <h:outputText value="#{kunde.kundenNr}"/>
            </h:column>
            <h:column>
                <f:facet name="header">
                    <h:outputText value="Name" />
                </f:facet>
                <h:outputText value="#{kunde.name}"/>
            </h:column>
            <h:column>
                <f:facet name="header">
                    <h:outputText value="Vorname" />
                </f:facet>
                <h:outputText value="#{kunde.vorname}"/>
            </h:column>
            <h:column>
                <h:outputLink>Details</h:outputLink>
            </h:column>
        </h:dataTable>
    </ui:define>
</ui:composition>
</body>
</html>

And here is the backing-bean. The method getKunden is called multiple times:

@ManagedBean
@SessionScoped
public class KundenBackingBean extends AbstractBackingBean {

    private String nameFilterPattern;

    public List<Kunde> getKunden(){
        System.out.println("getKunden");
        return getApplication().getKunden(getNameFilterPattern());
    }

    public String getNameFilterPattern() {
        return nameFilterPattern;
    }

    public void setNameFilterPattern(String nameFilterPattern) {
        System.out.println("Name filter: " + nameFilterPattern);
        this.nameFilterPattern = nameFilterPattern;
    }

}
Emlyn answered 16/2, 2010 at 10:57 Comment(3)
is it always called the same number of times?Imam
do you use your IDE debug mode for verifying that methods called multiple times or you see it in logs?Indeciduous
It seems like at the first request it is called 8 times and after that it is called 21 times. I use eclipse + glassfish and started it in debug mode. I simply inserted an sysout into the method and counted the console outputs.Emlyn
D
11

The getters of a bean are just there to access model data from the view side. They can be called multiple times. Usually one or two times, but this can grow up to hundreds of times, especially when also used in UIData components or in other attributes than value (like rendered, disabled, etc). This does normally not harm, as it's just a simple method-invocation and doing expensive data loading logic or calculations is usually not to be done in the getters. Preloading/initializing is usually to be done in the bean constructor and/or bean action methods. Getters should in fact only return the data (if necessary also do lazy loading).

If getApplication().getKunden(getNameFilterPattern()); is doing a pretty expensive task, you should really move it to either the bean constructor, or bean @PostConstruct method, or bean initialization block, or bean action method, or introduce lazy loading pattern in the getter. Here's an example which shows how to do this all:

public class Bean {
    private String nameFilterPattern;
    private List<Kunde> kunden;

    // Load during bean construction.
    public Bean() {
        this.kunden = getApplication().getKunden(getNameFilterPattern());
    }

    // OR load during @PostConstruct (will be invoked AFTER construction and resource injection.
    @PostConstruct
    public void init() {
        this.kunden = getApplication().getKunden(getNameFilterPattern());
    }

    // OR during bean initialization (this is invoked BEFORE construction and will apply to ALL constructors).
    {
        this.kunden = getApplication().getKunden(getNameFilterPattern());
    }

    // OR during bean action method (invoked from h:commandLink/Button).
    public String submit() {
        this.kunden = getApplication().getKunden(getNameFilterPattern());
        return "navigationCaseOutcome";
    }

    // OR using lazy loading pattern in getter method.
    public List<Kunde> getKunden() {
        if (this.kunden == null) 
            this.kunden = getApplication().getKunden(getNameFilterPattern());
        }
        return this.kunden;
    }

In your specific case, I think it's the @PostConstruct (if the nameFilterPattern is to be obtained from a GET request parameter), or just the bean action method (if nameFilterPattern is to be obtained from a POST form input field) is suitable.

To learn more about the JSF lifecycle, you may find this self-practice article useful.

Dockery answered 16/2, 2010 at 11:46 Comment(2)
Nice answer, btw :) #2090533Phantasmagoria
Yes, it's certainly not the first time I answered like that :) I've aswered it at least 20 times before. Not only here, but also at forums.sun.com and so on.Dockery
P
2

It can be called from different phases of the JSF lifecylce. My bet would be the phases RestoreView and then RenderResponse -- I haven't been using JSF lately, so I don't remember this in detail.

You can cache the latest filter pattern and the corresponding clients. You reload the clients only if the filter changed. This way, you solve this particular problem, plus avoid reloading data if the filter didn't change.

private String nameFilterPattern;
private String lastNameFilterPatternLoaded;
private List<Kunde> clients;

public List<Kunde> getKunden(){
    System.out.println("getKunden");
    if( nameFilterPattern.equals( lastNameFilterPatternLoaded ) )
    {
        clients = getApplication().getKunden(getNameFilterPattern());
        lastNameFilterPatternLoaded = nameFilterPattern
    }
    return clients;
}

Or you can use a request bean (instead of session) and make sure you load the data only once per request.

Phantasmagoria answered 16/2, 2010 at 11:8 Comment(2)
Thanks for answering. I changed the bean to request scope. The behaviour is the same. How do I make sure to load the data only onces per request? As you can see in the facelet, the method is only referenced once. Could this isse be related the the template stuff: ui:composition / ui:define?Emlyn
The answer from BalusC that you've accepted covers it all. I guess you don't need further explanation.Phantasmagoria

© 2022 - 2024 — McMap. All rights reserved.