Почему геттер называется так много раз с помощью атрибута rendered?

В предыдущем примере я попытался отслеживать методы get/set на сервере (когда они вызывается и как часто). Итак, я действительно выглядел таким:

@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); 
    }
}

и единственная страница, которая может вызывать этот метод (он вызывает только метод get при визуализации):

<!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>

мой ступор, когда я вижу журнал сервера, и вижу:

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

Что? Он вызывает семь раз метод getProfilePage()? (а также 1 раз setProfilePage()) Я хотел бы знать, почему это поведение:)

Спасибо

ДОБАВИТЬ ПРИМЕР

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>      

В этом примере я вызываю profile_main (по умолчанию); После (например) я вызываю profile_edit (нажав на EDIT); После этого я вернусь к profile_main, нажав Назад. Теперь, если я хочу перезагрузить profile_edit (EDIT), мне нужно многократно нажимать на эту командную кнопку. Почему?

Ответ 1

EL (язык выражений, те #{} вещи) не будут кэшировать результат вызовов или так. Он просто обращается к данным прямо в bean. Обычно это не наносит вреда, если получатель просто возвращает данные.

Вызов сеттера выполняется @ManagedProperty. Он в основном делает следующее:

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

Геттерные вызовы выполняются rendered="#{selector.profilePage == 'some'}" во время фазы ответа рендеринга. Когда он оценивает false в первый раз, в UIComponent#encodeAll(), больше вызовов не будет. Когда он оценивает true, он будет повторно оцениваться еще шесть раз в следующей последовательности:

Компонент и его рендеринг проверяют на каждом шаге, если он разрешен для рендеринга. Во время отправки формы, если компонент ввода или команды или любой из его родителей имеет атрибут rendered, тогда он также будет оцениваться во время фазы запроса запроса как часть защиты от подделанных/взломанных запросов.

Правда, это выглядит как неуклюжий и неэффективный. Считалось, что ахиллесное исцеление JSF по спецификации 941. Было предложено удалить все эти повторные проверки и придерживаться того, что было сделано в UIComponent#encodeAll(), или оценить isRendered() пошагово. Во время обсуждения в EG стало ясно, что корень проблемы находится в EL, а не в JSF, и эта производительность может быть значительно улучшена с помощью CDI. Поэтому не было необходимости разрешать его со стороны спецификации JSF.


Если вы обеспокоены тем, что управляемое свойство должно быть проверено только один раз после его установки, если оно пустое или пустое, тогда подумайте о его переносе в метод, который аннотируется с помощью @PostConstruct. Такой метод будет вызываться непосредственно после построения bean и всех инъекций зависимостей.

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

См. также:

Ответ 2

вы можете использовать методы CDI Producers. Он будет вызываться много раз, но результат первого вызова кэшируется в области bean и эффективен для геттеров, которые вычисляют или инициализируют тяжелые объекты! См. здесь, для получения дополнительной информации.