1. ホーム
  2. パフォーマンス

[解決済み】JSFがゲッターを複数回呼び出す理由

2022-04-02 23:40:01

質問

例えば、次のようなoutputTextコンポーネントを指定するとします。

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

のゲッターの時にログメッセージを出力すると someProperty が呼び出され、ページを読み込むと、ゲッターが1回のリクエストにつき2回以上呼び出されていることに気づくのは簡単です (私の場合は2回か3回でした)。

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がこのような振る舞いをしないようにする方法はあるのでしょうか?

ご意見ありがとうございました。

解決方法は?

これは、遅延表現の性質に起因するものです #{} (ただし、レガシーな標準表現である ${} は、JSPの代わりにFaceletsを使用した場合でも全く同じ動作をします)。遅延式は すぐに として作成されます。 ValueExpression を呼び出すと、その式の背後にあるゲッターメソッドが毎回実行されます。 ValueExpression#getValue() .

これは通常、コンポーネントが入力コンポーネントか出力コンポーネントかによって、JSFのリクエスト・レスポンスサイクルごとに1回または2回呼び出されます( ここで学ぶ ). しかし、JSFコンポーネントを繰り返し使用する場合、このカウントは(はるかに)高くなることがあります ( <h:dataTable><ui:repeat> のようなブーリアン表現があちこちにあります)。 rendered 属性があります。JSF(具体的にはEL)はEL式の評価結果を全くキャッシュしないので かもしれない は、呼び出しごとに異なる値を返します (たとえば、現在反復処理されているデータテーブル行に依存している場合など)。

EL式の評価とゲッターメソッドの起動は非常に安価な処理ですので、一般的には全く気にする必要はありません。しかし、何らかの理由でゲッターメソッド内で高価なDB/ビジネスロジックを実行する場合には、話は変わってきます。これでは毎回再実行されてしまう!

JSFのバッキングビーンにおけるゲッターメソッドは、もっぱら以下のように設計されるべきです。 返す のように、すでに準備されたプロパティとそれ以外のものはありません。 Javabeansの仕様 . 高価なDB/ビジネスロジックは一切行うべきではありません。そのために,ビーンの @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;
    }

}

注意点としては ではなく なぜなら、CDI のようなプロキシを使用する Bean 管理フレームワークを使用している場合、 複数回起動される可能性があるからです。

もし他に方法がないのであれば、ゲッターメソッドの中にレイジーローディングを導入してください。つまり、もしプロパティが null を読み込んでプロパティに代入し、そうでなければそれを返します。

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

        return someProperty;
    }

こうすることで、高価なDB/ビジネスロジックが、ゲッター呼び出しのたびに不必要に実行されることがなくなります。

こちらもご覧ください。