Why is the getter called so many times by the rendered attribute?
Asked Answered
S

2

20

Related to a previous example, i tried to monitor my get/set methods on the server (when they are called, and how often). So, my actual been look such :

@ManagedBean(name="selector")
@RequestScoped
public class Selector {
    @ManagedProperty(value="#{param.profilePage}")
    private String profilePage;

    public String getProfilePage() {
        if(profilePage==null || profilePage.trim().isEmpty()) {
            this.profilePage="main";
        }

        System.out.println("GET "+profilePage);

        return profilePage;
    }
    public void setProfilePage(String profilePage) { 
        this.profilePage=profilePage; 
        System.out.println("SET "+profilePage); 
    }
}

and the only page who can call this method (it only calls the get method on rendered) is :

<!DOCTYPE html>
<ui:composition
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets">

    <h:panelGroup layout="block" id="profileContent">
        <h:panelGroup rendered="#{selector.profilePage=='main'}">
            // nothing at the moment
        </h:panelGroup>
    </h:panelGroup>
</ui:composition>

my stupor when i see the server log, and i see :

SET null
GET main
GET main
GET main
GET main
GET main
GET main
GET main

What? It call seven times the getProfilePage() method? (and also 1 time setProfilePage()) I would like to know why this behaviour :)

Thanks

ADDED AN EXAMPLE

Bean

@ManagedBean(name="selector")
@RequestScoped
public class Selector {
    @ManagedProperty(value="#{param.profilePage}")
    private String profilePage;

    @PostConstruct
    public void init() {
        if(profilePage==null || profilePage.trim().isEmpty()) {
            this.profilePage="main";
        }
    }

    public String getProfilePage() { return profilePage; }
    public void setProfilePage(String profilePage) { this.profilePage=profilePage; }
}

profile.xhtml

<h:panelGroup layout="block" id="profileContent">
    <h:panelGroup layout="block" styleClass="content_title">
        Profilo Utente
    </h:panelGroup>

    <h:panelGroup rendered="#{selector.profilePage=='main'}">
        <ui:include src="/profile/profile_main.xhtml" />
    </h:panelGroup>

    <h:panelGroup rendered="#{selector.profilePage=='edit'}">
        <ui:include src="/profile/profile_edit.xhtml" />
    </h:panelGroup>
</h:panelGroup>

// profile_main.xhtml
<h:form id="formProfileMain" prependId="false">
    <h:panelGroup layout="block" styleClass="content_span">
        <h:outputScript name="jsf.js" library="javax.faces" target="head" />

        <h:panelGroup layout="block" styleClass="profilo_3">
            <h:commandButton value="EDIT">
                <f:setPropertyActionListener target="#{selector.profilePage}" value="edit" />
                <f:ajax event="action" render=":profileContent"/>
            </h:commandButton>
        </h:panelGroup>
    </h:panelGroup>
</h:form>

// profile_edit.xhtml
<h:form id="formProfileEdit" prependId="false">
    <h:panelGroup layout="block" styleClass="content_span">
        <h:outputScript name="jsf.js" library="javax.faces" target="head" />

        <h:panelGroup layout="block" styleClass="profilo_3">
            <h:commandButton value="Edit">
                <f:setPropertyActionListener target="#{selector.profilePage}" value="editProfile" />
                <f:ajax event="action" render=":profileContent"/>
            </h:commandButton>

            <h:commandButton value="Back">
                <f:setPropertyActionListener target="#{selector.profilePage}" value="main" />
                <f:ajax event="action" render=":profileContent"/>
            </h:commandButton>
        </h:panelGroup>
    </h:panelGroup>
</h:form>      

In this example, i call the profile_main (as default); After (for example) I call profile_edit (by clicking on EDIT); After, I return to profile_main by clicking Back. Now, if i want to reload profile_edit (EDIT), i need to click many times on that command button. Why?

Skvorak answered 25/11, 2010 at 22:28 Comment(2)
Probably duplicate of this question: stackoverflow.com/questions/2090033/…Berl
I readed the article, but it doesnt explain why the getMethod are called many times. And this make to me some problems now with AJAX call (seems that write/rewrite some bean property).Skvorak
V
38

EL (Expression Language, those #{} things) won't cache the result of the calls or so. It just accesses the data straight in the bean. This does normally not harm if the getter just returns the data.

The setter call is done by @ManagedProperty. It basically does the following:

selector.setProfilePage(request.getParameter("profilePage"));

The getter calls are all done by rendered="#{selector.profilePage == 'some'}" during the render response phase. When it evaluates false the first time, in UIComponent#encodeAll(), then no more calls will be done. When it evaluates true, then it will be re-evaluated six more times in the following sequence:

  1. UIComponent#encodeBegin() - Locates renderer for the begin of component.
  2. Renderer#encodeBegin() - Renders begin of component.
  3. UIComponent#encodeChildren() - Locates renderer for children of component.
  4. Renderer#encodeChildren() - Renders children of component.
  5. UIComponent#encodeEnd() - Locates renderer for end of component.
  6. Renderer#encodeEnd() - Renders end of component.

The component and its renderer verifies during every step if it is allowed to render. During a form submit, if an input or command component or any of its parents has a rendered attribute, then it will also be evaluated during apply request values phase as part of safeguard against tampered/hacked requests.

True, this look like clumsy and inefficient. It was considered the achilles heal of JSF as per spec issue 941. It's been suggested to remove all those repeated checks and stick to the one done in UIComponent#encodeAll(), or to evaluate isRendered() on a per-phase basis. During EG discussion, it became clear the root of the problem is in EL, not in JSF, and that performance could be greatly improved with CDI. So there was no necessity to solve it from JSF spec side on.


If your concern is that the managed property should be checked only once after its setting if it's null or empty, then consider to move it into a method which is annotated with @PostConstruct. Such a method will be called directly after bean's construction and all dependency injection.

@PostConstruct
public void init() {
    if (profilePage == null || profilePage.trim().isEmpty()) {
        profilePage = "main";
    }
}

See also:

Vocalise answered 25/11, 2010 at 23:51 Comment(6)
Yes, but how I have specified, i have only one #{selector.profilePage} in only one page. Thats what I don't understand :)Skvorak
I haven't observed the behaviour in JSF 2.0 closely, it isn't worth the effort so microoptimize it since it's particularly cheap, but you may want to add a Thread.dumpStack(); to the getter to learn where this call originates and a sysout of FacesContext.getCurrentInstance().getPhaseId() to learn which phase it's sitting in now.Vocalise
Uhm ok, so is more complicate to understand this :) I think i shouldn't mind about it when programming on jsf (just i wanted to know if i done somethings wrong). But seems to happen to all. So ok, i initialize it after PostConstruct :)Skvorak
Added an example. If this topic will be closed, or the question is unrelated, i'll open a new one :) Let me knowSkvorak
Nice one man :) So, if i use @ManagedProperty (that such as selector.setProfilePage(request.getParameter("profilePage"));) and i use this value only to get param (not my case) i could remove the public void setProfilePage(String profilePage) { this.profilePage=profilePage; } (i wont do it, its a bean, maybe i'll use this in a future). Now is clear why it calls many times the getter method :) What i don't understand is why my code still doesnt work properly : I must click many time in the button to set profilePage attribute (the ajax call start, but seems that bean doesnt update itselfSkvorak
As addiction, i can say that (when ajax work) it pass to the server a parameter that it doesnt pass when the ajax fail. The param is javax.faces.ViewStateSkvorak
M
3

you can use CDI Producers methods. It will be called many times, but the result of first call is cached in scope of the bean and is efficient for getters that are computing or initializing heavy objects! See here, for more info.

Maquette answered 11/6, 2012 at 12:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.