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

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

问题描述

与前面的示例相关,我尝试在服务器上监视我的 get/set 方法(调用它们的时间和频率).所以,我的实际情况是这样的:

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

并且唯一可以调用此方法的页面(它仅在呈现时调用 get 方法)是:

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

什么?它调用了七次 getProfilePage() 方法?(还有 1 次 setProfilePage())我想知道为什么会出现这种行为:)

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

谢谢

添加示例

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

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(表达式语言,那些 #{} 的东西)不会缓存调用的结果.它只是直接访问 bean 中的数据.如果 getter 只是返回数据,这通常不会造成伤害.

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.

setter 调用由 @ManagedProperty 完成.它主要执行以下操作:

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

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

getter 调用全部由 rendered="#{selector.profilePage == 'some'}" 在渲染响应阶段完成.当它第一次评估 false 时,在 UIComponent#encodeAll(),那么就不会再调用了.当它评估 true 时,它将按以下顺序再评估六次:

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() - 定位组件开头的渲染器.
  2. Renderer#encodeBegin() - 渲染组件的开始.
  3. UIComponent#encodeChildren() - 为组件的子组件定位渲染器.
  4. Renderer#encodeChildren() - 渲染组件的子级.
  5. UIComponent#encodeEnd() - 定位组件结束的渲染器.
  6. Renderer#encodeEnd() - 渲染组件结束.

组件及其渲染器在每一步都验证是否允许渲染.在表单提交期间,如果输入或命令组件或其任何父组件具有 rendered 属性,那么它也将在应用请求值阶段进行评估,作为防止篡改/黑客请求的保护措施的一部分.

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.

确实,这看起来笨拙且效率低下.根据 规范问题 941,它被认为是 JSF 的致命伤.建议删除所有这些重复检查并坚持在 UIComponent#encodeAll() 中完成的检查,或者在每个阶段的基础上评估 isRendered().在EG讨论期间,很明显问题的根源在于 EL,而不是在 JSF,并且使用 CDI 可以大大提高性能.所以没有必要从 JSF 规范方面解决它.

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.

如果您担心托管属性在其设置为 null 或为空时只应检查一次,则考虑将其移动到使用 @PostConstruct 注释的方法中.这样的方法会在bean的构建和所有依赖注入之后直接调用.

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

另见:

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

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