【问题标题】:Why is the getter called so many times by the rendered attribute?为什么 getter 被渲染属性调用了这么多次?
【发布时间】:2011-05-15 22:33:59
【问题描述】:

与前面的示例相关,我尝试监控服务器上的 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(编辑),我需要多次单击该命令按钮。为什么?

【问题讨论】:

  • 这个问题可能重复:stackoverflow.com/questions/2090033/…
  • 我阅读了这篇文章,但它没有解释为什么 getMethod 被多次调用。这给我带来了 AJAX 调用的一些问题(似乎写/重写了一些 bean 属性)。

标签: jsf jsf-2 facelets getter


【解决方案1】:

EL(表达式语言,那些#{} 的东西)不会缓存调用的结果。它只是直接访问 bean 中的数据。如果 getter 只是返回数据,这通常不会造成损害。

setter 调用由@ManagedProperty 完成。它基本上做了以下工作:

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

getter 调用都是由rendered="#{selector.profilePage == 'some'}" 在渲染响应阶段完成的。当它第一次在UIComponent#encodeAll() 中评估false 时,将不再进行调用。当它评估true 时,它将按以下顺序再评估六次:

  1. UIComponent#encodeBegin() - 定位组件开头的渲染器。
  2. Renderer#encodeBegin() - 渲染组件的开头。
  3. UIComponent#encodeChildren() - 定位组件子级的渲染器。
  4. Renderer#encodeChildren() - 渲染组件的子组件。
  5. UIComponent#encodeEnd() - 定位组件末尾的渲染器。
  6. Renderer#encodeEnd() - 渲染组件的结尾。

组件及其渲染器在每个步骤中验证是否允许渲染。在表单提交期间,如果输入或命令组件或其任何父组件具有 rendered 属性,那么它还将在应用请求值阶段进行评估,以防止被篡改/黑客请求。

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


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

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

另见:

【讨论】:

  • 是的,但是我已经指定了,我只有一个#{selector.profilePage} 在只有one 页面。那是我不明白的:)
  • 我没有仔细观察 JSF 2.0 中的行为,不值得付出努力,因此对其进行微优化,因为它特别便宜,但您可能需要在 getter 中添加 Thread.dumpStack(); 以学习此调用的来源和FacesContext.getCurrentInstance().getPhaseId() 的系统输出以了解它现在处于哪个阶段。
  • 嗯好的,所以理解这一点更复杂:) 我认为在 jsf 上编程时我不应该介意它(只是我想知道我是否做错了什么)。但似乎发生在所有人身上。好吧,我在 PostConstruct 之后初始化它:)
  • 添加了一个例子。如果这个话题将被关闭,或者问题无关,我会开一个新的:)让我知道
  • 好人 :) 所以,如果我使用@ManagedProperty(例如selector.setProfilePage(request.getParameter("profilePage"));)并且我只使用这个值来获取参数(不是我的情况)我可以删除public void setProfilePage(String profilePage) { this.profilePage=profilePage; } (我不会这样做,它是一个 bean,也许我将来会使用它)。现在很清楚为什么它会多次调用 getter 方法:) 我不明白的是为什么我的代码仍然无法正常工作:我必须在按钮中单击多次才能设置 profilePage 属性(ajax 调用开始,但似乎 bean不更新自己
【解决方案2】:

您可以使用 CDI Producers 方法。 它会被多次调用,但第一次调用的结果被缓存在 bean 的范围内,对于计算或初始化重对象的 getter 来说是有效的! 请参阅here,了解更多信息。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-27
    • 1970-01-01
    • 2014-09-01
    相关资源
    最近更新 更多