为什么 JSF 会多次调用 getter [英] Why JSF calls getters multiple times

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

问题描述

假设我像这样指定一个 outputText 组件:

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

DEBUG 2010-01-18 23:31:40,104 (ManagedBean.java:13) - 获取一些属性DEBUG 2010-01-18 23:31:40,104 (ManagedBean.java:13) - 获得一些财产

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

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

private String someProperty;公共字符串 getSomeProperty() {如果(this.someProperty == null){this.someProperty = this.calculatePropertyValue();}返回 this.someProperty;}

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

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

感谢您的意见!

解决方案

这是由延迟表达式的性质引起的#{}(注意遗留"标准表达式${} 使用 Facelets 而不是 JSP 时的行为完全相同).延迟表达式不会立即求值,而是创建为 ValueExpression 对象和表达式背后的 getter 方法在每次代码调用 ValueExpression#getValue().

这通常会在每个 JSF 请求-响应周期调用一到两次,具体取决于组件是输入组件还是输出组件(在这里学习).但是,当用于迭代 JSF 组件(例如 )时,这个计数会增加(很多),或者在像 rendered 属性这样的布尔表达式中到处都是.JSF(特别是 EL)根本不会缓存 EL 表达式的计算结果,因为它可能在每次调用时返回不同的值(例如,当它依赖于当前迭代的数据表行时).

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

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

这里总结了所有不同的正确预设/加载属性方法.

公共类Bean {私人 SomeObject someProperty;@PostConstruct公共无效初始化(){//在@PostConstruct 中(将在构造和依赖/属性注入后立即调用).someProperty = loadSomeProperty();}公共无效加载(){//或者在 GET 操作方法中(例如 ).someProperty = loadSomeProperty();}公共无效预渲染(组件系统事件事件){//或者在一些 SystemEvent 方法中(例如 <f:event type="preRenderView">).someProperty = loadSomeProperty();}公共无效更改(ValueChangeEvent 事件){//或者在某些 FacesEvent 方法中(例如 ).someProperty = loadSomeProperty();}公共无效ajaxListener(AjaxBehaviorEvent事件){//或者在一些 BehaviorEvent 方法中(例如 <f:ajax listener>).someProperty = loadSomeProperty();}public void actionListener(ActionEvent event) {//或者在某些 ActionEvent 方法中(例如 ).someProperty = loadSomeProperty();}公共字符串提交(){//或者在 POST 操作方法中(例如 ).someProperty = loadSomeProperty();返回结果";}公共 SomeObject getSomeProperty() {//保持 getter 不变.它不打算做业务逻辑!返回一些属性;}}

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

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

 public SomeObject getSomeProperty() {//如果实在没有其他办法,就引入延迟加载.如果(someProperty == null){someProperty = loadSomeProperty();}返回一些属性;}

这样就不会在每次 getter 调用中不必要地执行昂贵的数据库/业务逻辑.

另见:

Let's say I specify an outputText component like this:

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

If I print a log message when the getter for someProperty is called and load the page, it is trivial to notice that the getter is being called more than once per request (twice or three times is what happened in my case):

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

If the value of someProperty is expensive to calculate, this can potentially be a problem.

I googled a bit and figured this is a known issue. One workaround was to include a check and see if it had already been calculated:

private String someProperty;

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

The main problem with this is that you get loads of boilerplate code, not to mention private variables that you might not need.

What are the alternatives to this approach? Is there a way to achieve this without so much unnecessary code? Is there a way to stop JSF from behaving in this way?

Thanks for your input!

解决方案

This is caused by the nature of deferred expressions #{} (note that "legacy" standard expressions ${} behave exactly the same when Facelets is used instead of JSP). The deferred expression is not immediately evaluated, but created as a ValueExpression object and the getter method behind the expression is executed everytime when the code calls ValueExpression#getValue().

This will normally be invoked one or two times per JSF request-response cycle, depending on whether the component is an input or output component (learn it here). However, this count can get up (much) higher when used in iterating JSF components (such as <h:dataTable> and <ui:repeat>), or here and there in a boolean expression like the rendered attribute. JSF (specifically, EL) won't cache the evaluated result of the EL expression at all as it may return different values on each call (for example, when it's dependent on the currently iterated datatable row).

Evaluating an EL expression and invoking a getter method is a very cheap operation, so you should generally not worry about this at all. However, the story changes when you're performing expensive DB/business logic in the getter method for some reason. This would be re-executed everytime!

Getter methods in JSF backing beans should be designed that way that they solely return the already-prepared property and nothing more, exactly as per the Javabeans specification. They should not do any expensive DB/business logic at all. For that the bean's @PostConstruct and/or (action)listener methods should be used. They are executed only once at some point of request-based JSF lifecycle and that's exactly what you want.

Here is a summary of all different right ways to preset/load a property.

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

}

Note that you should not use bean's constructor or initialization block for the job because it may be invoked multiple times if you're using a bean management framework which uses proxies, such as CDI.

If there are for you really no other ways, due to some restrictive design requirements, then you should introduce lazy loading inside the getter method. I.e. if the property is null, then load and assign it to the property, else return it.

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

        return someProperty;
    }

This way the expensive DB/business logic won't unnecessarily be executed on every single getter call.

See also:

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

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