Почему метод get так часто вызывается атрибутом рендеринга? - PullRequest
17 голосов
/ 26 ноября 2010

По сравнению с предыдущим примером я пытался отслеживать мои методы 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), мне нужно много раз нажать на эту кнопку команды. Почему?

Ответы [ 2 ]

34 голосов
/ 26 ноября 2010

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";
    }
}

См. Также:

3 голосов
/ 11 июня 2012

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

...