web-dev-qa-db-ja.com

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

次のように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がこのように動作するのを止める方法はありますか?

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

251
Sevas

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

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

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

JSFバッキングBeanのゲッターメソッドは、returnすでに準備されたプロパティのみ Javabeans仕様 。高価なDB /ビジネスロジックをまったく使用しないでください。そのためには、Beanの@PostConstructおよび/または(アクション)リスナーメソッドを使用する必要があります。それらは、リクエストベースのJSFライフサイクルのある時点で一度だけ実行されます一度だけ、それがまさにあなたが望むものです。

以下は、プロパティをプリセット/ロードするためのさまざまな right 方法の要約です。

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

}

notは、Beanのコンストラクターまたは初期化ブロックをジョブに使用する必要があることに注意してください。 CDIなどのプロキシ。

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

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

        return someProperty;
    }

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

こちらもご覧ください:

333
BalusC

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>
16
César Alforde

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

}

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

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

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

9
Nicolas Labrot

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

最近、アプリのパフォーマンスの評価、JPAクエリの調整、動的SQLクエリの名前付きクエリへの置き換えに夢中になっており、今朝、ゲッターメソッドはJavaのHOT SPOTであることがわかりましたVisual VMは、残りのコード(またはコードの大部分)よりも優れています。

ゲッターメソッド:

PageNavigationController.getGmapsAutoComplete()

Index.xhtmlのui:includeで参照

以下では、PageNavigationController.getGmapsAutoComplete()がJava Visual VMのHOT SPOT(パフォーマンスの問題)であることがわかります。さらに下を見ると、スクリーンキャプチャで、PrimeFacesレイジーデータテーブルゲッターメソッドgetLazyModel()もホットスポットであることがわかります。これは、エンドユーザーが大量の「レイジーデータテーブル」タイプの操作/タスク/タスクを実行している場合のみですアプリで。 :)

Java Visual VM: showing 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」メソッドにコードを追加しないようアドバイスしているため、このトピックを共有しています。 :)

6
Howard

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

4
Heidarzadeh

おそらく、AOPを使用して、設定可能な時間にわたってゲッターの結果をキャッシュするある種のアスペクトを作成できます。これにより、数十個のアクセサに定型コードをコピーアンドペーストする必要がなくなります。

3
matt b

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

セキュリティチェックは複雑になる場合があります。 LDAPクエリなど。

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

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

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

0
Morad