JSFがゲッターを複数回呼び出す理由


256

次のようにoutputTextコンポーネントを指定するとします。

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

のゲッターsomePropertyが呼び出されたときにログメッセージを出力してページをロードすると、ゲッターがリクエストごとに複数回呼び出されていることに気付くのは簡単です(私の場合は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計算にコストがかかる場合、これが問題になる可能性があります。

私は少しグーグルで調べて、これは既知の問題であると考えました。回避策の1つは、チェックを含め、それがすでに計算されているかどうかを確認することでした。

private String someProperty;

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

これの主な問題は、不要なプライベート変数は言うまでもなく、大量のボイラープレートコードを取得することです。

このアプローチの代替手段は何ですか?それほど多くの不要なコードなしでこれを達成する方法はありますか?JSFがこのように動作しないようにする方法はありますか?

ご協力ありがとうございます。

回答:


340

これは、遅延式の性質が原因で発生します#{}(「レガシー」標準式${}は、JSPの代わりにFaceletsを使用する場合もまったく同じように動作することに注意してください)。遅延式はすぐには評価されませんが、ValueExpressionオブジェクトとして作成され、コードの呼び出し時に毎回、式の背後にあるゲッターメソッドが実行されますValueExpression#getValue()

これは通常、コンポーネントが入力コンポーネントであるか出力コンポーネントであるかに応じて、JSF要求/応答サイクルごとに1回または2回呼び出されます(こちらをご覧ください)。ただし、この数は、JSFコンポーネント(<h:dataTable>およびなど<ui:repeat>)の反復で使用した場合、またはrendered属性のようなブール式のあちこちにある場合、(はるかに)高くなる可能性があります。JSF(具体的には、EL)はEL式の評価結果をキャッシュしません。これ、呼び出しごとに異なる値を返す可能性があるためです(たとえば、現在反復されているデータテーブル行に依存している場合)。

EL式を評価してゲッターメソッドを呼び出すことは、非常に安価な操作であるため、通常、これについてまったく心配する必要はありません。ただし、何らかの理由でゲッターメソッドで高額なDB /ビジネスロジックを実行していると、話は変わります。これは毎回再実行されます!

JSFバッキングBeanのゲッターメソッドは、Javabeans仕様とまったく同じように、すでに準備されたプロパティのみを返すように設計する必要があります。高価なDB /ビジネスロジックをまったく実行すべきではありません。そのため、Beanのリスナメソッドや(アクション)リスナメソッドを使用する必要があります。これらは、リクエストベースのJSFライフサイクルのある時点で1回だけ実行され、まさにそれが望みどおりです。@PostConstruct

プロパティをプリセット/ロードするための正しい方法すべての概要を以下に示します。

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のコンストラクタまたは初期化ブロックを使用しています。

制限のある設計要件のために本当に他に方法がない場合は、getterメソッド内に遅延読み込みを導入する必要があります。つまり、プロパティがの場合、nullロードしてプロパティに割り当て、そうでない場合はそれを返します。

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

        return someProperty;
    }

このようにして、高価なDB /ビジネスロジックがゲッターコールごとに不必要に実行されることはありません。

以下も参照してください。


5
ビジネスロジックを実行するためにゲッターを使用しないでください。それで全部です。コードロジックを再配置します。コンストラクター、ポストコンストラクター、またはアクションメソッドをスマートに使用するだけで、すでに修正されているに違いありません。
BalusC 2010年

3
-1、強く反対。JavaBeansの仕様の全体のポイントはである可能プロパティは単なるフィールド値以上であると、その場で計算された「派生プロパティは、」完全に正常です。冗長なゲッターコールを心配することは、時期尚早の最適化に他なりません。
Michael Borgwardt、2010年

3
あなたが明確にあなた自身を述べたように彼らがデータを返す以上のことをすると期待してください:)
BalusC '19

4
ゲッターでの遅延初期化はJSFでも有効であることを追加できます:)
Bozho

2
@ハリー:それは行動を変えることはありません。ただし、遅延読み込みや現在のフェーズIDをで確認することにより、条件付きでゲッターのビジネスロジックを処理できますFacesContext#getCurrentPhaseId()
BalusC

17

JSF 2.0を使用すると、リスナーをシステムイベントに接続できます。

<h:outputText value="#{ManagedBean.someProperty}">
   <f:event type="preRenderView" listener="#{ManagedBean.loadSomeProperty}" />
</h:outputText>

または、JSFページをf:viewタグで囲むこともできます。

<f:view>
   <f:event type="preRenderView" listener="#{ManagedBean.loadSomeProperty}" />

      .. jsf page here...

<f:view>

9

Spring AOPでJSF Beanゲッターをキャッシュする方法についての記事を書きました。

MethodInterceptor特別なアノテーションが付けられたすべてのメソッドをインターセプトするシンプルなものを作成します。

public class CacheAdvice implements MethodInterceptor {

private static Logger logger = LoggerFactory.getLogger(CacheAdvice.class);

@Autowired
private CacheService cacheService;

@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {

    String key = methodInvocation.getThis() + methodInvocation.getMethod().getName();

    String thread = Thread.currentThread().getName();

    Object cachedValue = cacheService.getData(thread , key);

    if (cachedValue == null){
        cachedValue = methodInvocation.proceed();
        cacheService.cacheData(thread , key , cachedValue);
        logger.debug("Cache miss " + thread + " " + key);
    }
    else{
        logger.debug("Cached hit " + thread + " " + key);
    }
    return cachedValue;
}


public CacheService getCacheService() {
    return cacheService;
}
public void setCacheService(CacheService cacheService) {
    this.cacheService = cacheService;
}

}

このインターセプターは、Spring構成ファイルで使用されます。

    <bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
    <property name="pointcut">
        <bean class="org.springframework.aop.support.annotation.AnnotationMatchingPointcut">
            <constructor-arg index="0"  name="classAnnotationType" type="java.lang.Class">
                <null/>
            </constructor-arg>
            <constructor-arg index="1" value="com._4dconcept.docAdvance.jsfCache.annotation.Cacheable" name="methodAnnotationType" type="java.lang.Class"/>
        </bean>
    </property>
    <property name="advice">
        <bean class="com._4dconcept.docAdvance.jsfCache.CacheAdvice"/>
    </property>
</bean>

お役に立てれば幸いです。


6

もともとPrimeFacesフォーラムに投稿されました@ http://forum.primefaces.org/viewtopic.php?f=3&t=29546

最近、アプリのパフォーマンスの評価、JPAクエリのチューニング、動的SQLクエリの名前付きクエリへの置き換えに夢中になっており、今朝、ゲッターメソッドがJava Visual VMの他の部分よりもホットスポットであることを認識しました私のコード(または私のコードの大部分)。

ゲッターメソッド:

PageNavigationController.getGmapsAutoComplete()

index.xhtmlのui:includeで参照

以下では、PageNavigationController.getGmapsAutoComplete()がJava Visual VMのホットスポット(パフォーマンスの問題)であることがわかります。さらに下を見ると、スクリーンキャプチャで、PrimeFacesのレイジーデータテーブルゲッターメソッドであるgetLazyModel()もホットスポットであることがわかります。アプリで。:)

Java Visual VM:HOT SPOTの表示

以下の(元の)コードを参照してください。

public Boolean getGmapsAutoComplete() {
    switch (page) {
        case "/orders/pf_Add.xhtml":
        case "/orders/pf_Edit.xhtml":
        case "/orders/pf_EditDriverVehicles.xhtml":
            gmapsAutoComplete = true;
            break;
        default:
            gmapsAutoComplete = false;
            break;
    }
    return gmapsAutoComplete;
}

index.xhtmlで次のように参照されます。

<h:head>
    <ui:include src="#{pageNavigationController.gmapsAutoComplete ? '/head_gmapsAutoComplete.xhtml' : (pageNavigationController.gmaps ? '/head_gmaps.xhtml' : '/head_default.xhtml')}"/>
</h:head>

解決策:これは「getter」メソッドであるため、メソッドが呼び出される前に、コードを移動してgmapsAutoCompleteに値を割り当てます。以下のコードを参照してください。

/*
 * 2013-04-06 moved switch {...} to updateGmapsAutoComplete()
 *            because performance = 115ms (hot spot) while
 *            navigating through web app
 */
public Boolean getGmapsAutoComplete() {
    return gmapsAutoComplete;
}

/*
 * ALWAYS call this method after "page = ..."
 */
private void updateGmapsAutoComplete() {
    switch (page) {
        case "/orders/pf_Add.xhtml":
        case "/orders/pf_Edit.xhtml":
        case "/orders/pf_EditDriverVehicles.xhtml":
            gmapsAutoComplete = true;
            break;
        default:
            gmapsAutoComplete = false;
            break;
    }
}

テスト結果:PageNavigationController.getGmapsAutoComplete()は、Java Visual VMのホットスポットではなくなりました(表示されなくなりました)。

多くのエキスパートユーザーがジュニアJSF開発者に「getter」メソッドにコードを追加しないようにアドバイスしているため、このトピックを共有します。:)


4

CDIを使用している場合は、Producersメソッドを使用できます。何度も呼び出されますが、最初の呼び出しの結果はBeanのスコープにキャッシュされ、重いオブジェクトを計算または初期化するゲッターにとって効率的です!詳細についてこちらをご覧ください。


3

おそらく、AOPを使用して、ゲッターの結果を構成可能な時間キャッシュする一種のアスペクトを作成できます。これにより、何十ものアクセサでボイラープレートコードをコピーして貼り付ける必要がなくなります。


あなたが話しているこの春のAOPは?アスペクトを扱うコードスニペットがどこにあるか知っていますか?Springのドキュメントの第6章全体を読むのは、私がSpringを使用していないのでやり過ぎのようです;)
Sevas

-1

somePropertyの値の計算にコストがかかる場合、これが問題になる可能性があります。

これは時期尚早の最適化と呼ばれるものです。プロファイラーがプロパティの計算に非常にコストがかかり、1回ではなく3回呼び出すとパフォーマンスに大きな影響を与えるというまれなケースでは、説明したようにキャッシュを追加します。しかし、素数の因数分解やゲッターでのデータベースへのアクセスなど、本当に愚かなことをしない限り、コードには、考えたことのない場所で12のひどい非効率性があります。


したがって、質問-somePropertyが計算に高価なものに対応している場合(またはデータベースにアクセスしたり素数を分解したりする場合)、リクエストごとに数回計算を回避するための最良の方法は何ですか?質問に記載した解決策です一番いいもの。質問に答えない場合は、コメントを投稿するのに適しています。また、あなたの投稿はBalusCの投稿に対するコメントと矛盾しているようです。コメントの中で、その場で計算を行うのは問題ないと言っていて、あなたの投稿ではそれがばかげていると言っています。どこに線を引くか聞いてもいいですか?
Sevas、2010年

これはスライド式のスケールであり、白黒の問題ではありません。100万分の1秒未満(実際にははるかに短い)なので、いくつかの値は明らかに問題ではありません。たとえば、いくつかの値を追加します。DBやファイルへのアクセスなど、明らかに10ms以上かかる場合があるため、明らかに問題があります。ゲッターだけでなく、可能であれば回避できるように、これらを確実に知る必要があります。しかし、他のすべてについては、ラインはプロファイラーがあなたに言うところです。
Michael Borgwardt、2010年

-1

ストックJSFの代わりにPrimefacesなどのフレームワークを使用することもお勧めします。JSFチームeの前に、このような問題に対処します。プライムフェイスでは、部分的な送信を設定できます。そうでなければ、BalusCはそれをうまく説明しています。


-2

それでもJSFの大きな問題です。たとえばisPermittedToBlaBla、セキュリティチェックのメソッドがあり、自分のビューにある場合rendered="#{bean.isPermittedToBlaBla}、そのメソッドは複数回呼び出されます。

たとえば、セキュリティチェックは複雑になる可能性があります。LDAPクエリなど。したがって、

Boolean isAllowed = null ... if(isAllowed==null){...} return isAllowed?

また、リクエストごとにセッションBean内でこれを確認する必要があります。

JSFはここで、複数の呼び出しを回避するためにいくつかの拡張機能を実装する必要があると思います(たとえば、注釈は@Phase(RENDER_RESPONSE)このメソッドをRENDER_RESPONSEフェーズの後に1回だけ呼び出します...)


2
結果をRequestParameterMapにキャッシュすることができます
Christophe Roussy
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.