小编典典

为什么 JSF 多次调用 getter

all

假设我指定了一个 outputText 组件,如下所示:

<h:outputText value="#{ManagedBean.someProperty}"/>

如果我在someProperty调用 getter for 并加载页面时打印一条日志消息,那么很容易注意到每个请求都多次调用
getter(在我的情况下发生了两次或三次):

DEBUG 2010-01-18 23:31:40,104 (ManagedBean.java:13) - Getting some property
DEBUG 2010-01-18 23:31:40,104 (ManagedBean.java:13) - Getting some property

如果 的值someProperty计算起来很昂贵,这可能是一个问题。

我用谷歌搜索了一下,发现这是一个已知问题。一种解决方法是检查是否已经计算过:

private String someProperty;

public String getSomeProperty() {
    if (this.someProperty == null) {
        this.someProperty = this.calculatePropertyValue();
    }
    return this.someProperty;
}

这样做的主要问题是你会得到大量的样板代码,更不用说你可能不需要的私有变量了。

这种方法有哪些替代方法?有没有办法在没有这么多不必要的代码的情况下实现这一点?有没有办法阻止 JSF 以这种方式行事?

感谢您的输入!


阅读 68

收藏
2022-05-13

共1个答案

小编典典

这是由延迟表达式的性质引起的#{}(请注意,${}当使用 Facelets 而不是 JSP 时,“遗留”标准表达式的行为完全相同)。延迟表达式不会
立即
计算,而是创建为ValueExpression对象,并且每次代码调用时都会执行表达式后面的
getter
方法ValueExpression#getValue()

这通常会在每个 JSF 请求-
响应周期中调用一到两次,具体取决于组件是输入组件还是输出组件(在此处了解)。但是,当用于迭代 JSF 组件(例如<h:dataTable>and
<ui:repeat>)时,或者在诸如rendered属性之类的布尔表达式中到处使用时,这个计数可能会上升(很多)。JSF(特别是
EL)根本不会缓存 EL 表达式的计算结果,因为它 可能 在每次调用时返回不同的值(例如,当它依赖于当前迭代的数据表行时)。

评估 EL 表达式并调用 getter 方法是一项非常便宜的操作,因此您通常根本不必担心这一点。但是,由于某种原因,当您在 getter
方法中执行昂贵的数据库/业务逻辑时,情况就会发生变化。每次都会重新执行!

JSF 支持 bean 中的 Getter 方法应该被设计成只 返回 已经准备好的属性,仅此而已,完全符合Javabeans
规范
。他们根本不应该做任何昂贵的数据库/业务逻辑。为此,应使用
bean 的@PostConstruct和/或(动作)侦听器方法。它们仅在基于请求的 JSF 生命周期的某个时间点执行 一次
,而这正是您想要的。

这是预设/加载属性的所有不同 正确 方法的摘要。

public class Bean {

    private SomeObject someProperty;

    @PostConstruct
    public void init() {
        // In @PostConstruct (will be invoked immediately after construction and dependency/property injection).
        someProperty = loadSomeProperty();
    }

    public void onload() {
        // Or in GET action method (e.g. <f:viewAction action>).
        someProperty = loadSomeProperty();
    }

    public void preRender(ComponentSystemEvent event) {
        // Or in some SystemEvent method (e.g. <f:event type="preRenderView">).
        someProperty = loadSomeProperty();
    }

    public void change(ValueChangeEvent event) {
        // Or in some FacesEvent method (e.g. <h:inputXxx valueChangeListener>).
        someProperty = loadSomeProperty();
    }

    public void ajaxListener(AjaxBehaviorEvent event) {
        // Or in some BehaviorEvent method (e.g. <f:ajax listener>).
        someProperty = loadSomeProperty();
    }

    public void actionListener(ActionEvent event) {
        // Or in some ActionEvent method (e.g. <h:commandXxx actionListener>).
        someProperty = loadSomeProperty();
    }

    public String submit() {
        // Or in POST action method (e.g. <h:commandXxx action>).
        someProperty = loadSomeProperty();
        return "outcome";
    }

    public SomeObject getSomeProperty() {
        // Just keep getter untouched. It isn't intented to do business logic!
        return someProperty;
    }

}

请注意,您不应 作业使用 bean 的构造函数或初始化块,因为如果您使用使用代理的 bean 管理框架(例如 CDI),它可能会被多次调用。

如果你真的没有其他方法,由于一些限制性的设计要求,那么你应该在 getter
方法中引入延迟加载。即如果属性是null,则加载并将其分配给该属性,否则返回它。

    public SomeObject getSomeProperty() {
        // If there are really no other ways, introduce lazy loading.
        if (someProperty == null) {
            someProperty = loadSomeProperty();
        }

        return someProperty;
    }

这样,昂贵的数据库/业务逻辑就不会在每个 getter 调用上不必要地执行。

2022-05-13