Почему метод get так часто вызывается атрибутом рендеринга?

В связи с предыдущим примером я пытался отслеживать мои методы 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()) Хотелось бы узнать, почему такое поведение:)

Спасибо

ДОБАВЛЕН ПРИМЕР

боб

@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), мне нужно много раз нажать на эту кнопку команды. Зачем?

2 ответа

Решение

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

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

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

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

  1. UIComponent#encodeBegin() - Находит рендерер для начала компонента.
  2. Renderer#encodeBegin() - Оказывает начало компонента.
  3. UIComponent#encodeChildren() - Находит рендер для дочерних компонентов.
  4. Renderer#encodeChildren() - Отрисовывает дочерние компоненты.
  5. UIComponent#encodeEnd() - Находит рендерер для конца компонента.
  6. Renderer#encodeEnd() - Оказывает конец компонента.

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

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


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

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

Смотрите также:

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

Другие вопросы по тегам