为什么getter被render属性多次调用? [英] Why is the getter called so many times by the rendered attribute?

查看:99
本文介绍了为什么getter被render属性多次调用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

与先前的示例有关,我尝试监视服务器上的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>

当我看到服务器日志时看到

我的stupor,

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),则需要在该命令按钮上单击多次.为什么?

解决方案

EL(表达语言,那些#{}东西)不会缓存调用结果.它只是直接在Bean中访问数据.如果吸气剂只是返回数据,这通常不会造成损害.

setter调用由@ManagedProperty完成.它基本上执行以下操作:

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

getter调用全部由rendered="#{selector.profilePage == 'some'}"在渲染响应阶段完成.第一次评估false时,在规格问题941 ,它被认为是JSF的致命弱点.建议删除所有那些重复的检查并坚持在UIComponent#encodeAll()中执行的检查,或在每个阶段评估isRendered(). 在EG讨论期间


如果您担心托管属性的设置为空或为空时仅应对其进行检查一次,那么请考虑将其移至用@PostConstruct注释的方法中.在bean的构造和所有依赖项注入之后,将直接调用这种方法.

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

另请参见:

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?

解决方案

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:

这篇关于为什么getter被render属性多次调用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆