264

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

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

getter が呼び出されたときにログ メッセージを出力しsomePropertyてページをロードすると、getter がリクエストごとに複数回呼び出されていることに気付くのは簡単です (私の場合は 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がこのように動作するのを止める方法はありますか?

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

4

9 に答える 9

352

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

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

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

JSFバッキングBeanのGetterメソッドは、 Javabeans仕様に従って、すでに準備されたプロパティのみを返し、それ以上は返さないように設計する必要があります。高価なDB/ビジネスロジックを実行するべきではありません。そのためには、Beanおよび/または(アクション)リスナーメソッドを使用する必要があります。それらはリクエストベースのJSFライフサイクルのある時点で一度だけ実行され、それはまさにあなたが望むものです。@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管理フレームワークを使用している場合は、Beanのコンストラクターまたは初期化ブロックが複数回呼び出される可能性があるため、ジョブにBeanのコンストラクターまたは初期化ブロックを使用しないでください

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

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

        return someProperty;
    }

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

参照:

于 2010-01-18T23:51:32.583 に答える
18

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>
于 2010-10-31T01:09:08.513 に答える
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>

それが役立つことを願っています!

于 2011-01-05T14:02:39.337 に答える
6

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

最近、私は自分のアプリのパフォーマンスを評価し、JPA クエリを調整し、動的 ​​SQL クエリを名前付きクエリに置き換えることに夢中になっています。私のコード(または私のコードの大部分)。

ゲッターメソッド:

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 の HOT SPOT ではありません (もう表示されません)。

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

于 2013-04-14T13:32:03.427 に答える
4

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

于 2012-06-11T11:50:13.007 に答える
3

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

于 2010-01-18T23:45:53.533 に答える
-1

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

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

于 2010-01-19T00:39:14.907 に答える
-2

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

セキュリティチェックは複雑になる可能性があります。LDAPクエリなど。したがって、それを避ける必要があります

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

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

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

于 2012-01-06T13:12:50.683 に答える