JSF neden alıcıları birden çok kez çağırıyor?


256

Diyelim ki böyle bir outputText bileşeni belirtiyorum:

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

Alıcı alındığında bir günlük mesajı yazdırırsam somePropertyve sayfayı yüklersem, alıcının istek başına bir kereden fazla çağrıldığını fark etmek önemsizdir (benim durumumda iki veya üç kez oldu):

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

Değerinin somePropertyhesaplanması pahalıysa, bu muhtemelen bir sorun olabilir.

Biraz googled ve bunun bilinen bir sorun olduğunu düşündüm. Bir geçici çözüm, bir çek eklemek ve daha önce hesaplanmış olup olmadığını görmekti:

private String someProperty;

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

Buradaki temel sorun, ihtiyaç duymayabileceğiniz özel değişkenlerden bahsetmemekle birlikte, bir sürü demirbaş kod almanızdır.

Bu yaklaşımın alternatifleri nelerdir? Bu kadar gereksiz kod olmadan bunu başarmanın bir yolu var mı? JSF'nin bu şekilde davranmasını engellemenin bir yolu var mı?

Girdiniz için teşekkürler!

Yanıtlar:


341

Bu, ertelenmiş ifadelerin doğasından kaynaklanır #{}("eski" standart ifadelerin ${}, JSP yerine Facelets kullanıldığında aynı şekilde davrandığını unutmayın ). Ertelenmiş ifade hemen değerlendirilmez, ancak bir ValueExpressionnesne olarak oluşturulur ve ifadenin arkasındaki getter yöntemi kod her çağrıldığında yürütülür ValueExpression#getValue().

Bu, bileşenin bir giriş veya çıkış bileşeni olmasına bağlı olarak, JSF istek yanıt döngüsü başına bir veya iki kez çağrılır ( buradan öğrenin ). Bununla birlikte, JSF bileşenlerini ( <h:dataTable>ve gibi <ui:repeat>) yinelemede veya burada ve burada renderedöznitelik gibi bir boole ifadesinde kullanıldığında bu sayı (çok) yükselebilir . Bu MTU (özellikle, EL) her EL ifadesinin değerlendirilmesi sonucu önbelleğe olmaz olabilir (mevcut Datatable satır tekrarlanır bağımlı olduğunda, örneğin) her çağrıda farklı değerler verir.

Bir EL ifadesini değerlendirmek ve bir alıcı yöntemini çağırmak çok ucuz bir işlemdir, bu nedenle genellikle bu konuda endişelenmemelisiniz. Ancak, nedense getter yönteminde pahalı DB / iş mantığı gerçekleştirdiğinizde hikaye değişir. Bu her seferinde yeniden yürütülür!

JSF destek fasulyesindeki Getter yöntemleri, sadece önceden hazırlanmış özelliği ve daha fazlasını, tam olarak Javabeans spesifikasyonuna göre döndürecek şekilde tasarlanmalıdır . Hiç pahalı DB / iş mantığı yapmamalıdırlar. Bunun için fasülyenin ve / veya (hareketli) dinleyici yöntemleri kullanılmalıdır. İsteğe bağlı JSF yaşam döngüsünün bir noktasında yalnızca bir kez yürütülürler ve tam olarak istediğiniz şey budur.@PostConstruct

Burada, bir mülkü önceden ayarlamak / yüklemek için tüm farklı doğru yolların bir özeti verilmiştir .

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

}

Eğer gerektiği Not değil böyle CDI olarak proxy, kullanan bir fasulye yönetim çerçevesini kullanıyorsanız birden çok kez başlatıldı edilebilir, çünkü bu iş için fasulye yapıcı veya başlatma bloğu kullanın.

Eğer sizin için başka hiçbir yol yoksa, bazı kısıtlayıcı tasarım gereksinimleri nedeniyle, alıcı yönteminde tembel yükleme yapmalısınız. Yani özellik ise null, yükleyip özelliğe atayın, aksi takdirde iade edin.

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

        return someProperty;
    }

Bu şekilde pahalı DB / iş mantığı, her bir alıcı çağrısında gereksiz yere yürütülmez.

Ayrıca bakınız:


5
Sadece iş mantığı yapmak için alıcıları kullanmayın. Bu kadar. Kod mantığınızı yeniden düzenleyin. Bahse girerim zaten yapıcı, postyapı veya eylem yöntemini akıllı bir şekilde kullanarak düzeltildi.
BalusC

3
-1, kesinlikle katılmıyorum. JavaBeans spesifikasyonunun tüm noktası, özelliklerin sadece bir alan değerinden daha fazla olmasına izin vermektir ve anında hesaplanan "türetilmiş özellikler" tamamen normaldir. Gereksiz alıcı çağrıları hakkında endişe etmek, erken optimizasyondan başka bir şey değildir.
Michael Borgwardt

3
Kendinizi açıkça ifade ettiğiniz gibi veri döndürmekten daha fazlasını yaparlarsa bekleyin :)
BalusC

4
getters'ta tembel başlatmanın hala JSF'de geçerli olduğunu ekleyebilirsiniz :)
Bozho

2
@Harry: Davranışı değiştirmez. Bununla birlikte, alıcıdaki herhangi bir iş mantığını tembel yükleme ve / veya mevcut faz kimliğini kontrol ederek koşullu olarak işleyebilirsiniz FacesContext#getCurrentPhaseId().
BalusC

17

JSF 2.0 ile bir sistem olayına dinleyici ekleyebilirsiniz

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

Alternatif olarak JSF sayfasını bir f:viewetikete ekleyebilirsiniz

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

      .. jsf page here...

<f:view>

9

Bahar AOP ile JSF fasulye alıcı önbellek nasıl hakkında bir makale yazdım .

MethodInterceptorÖzel bir açıklama ile açıklamalı tüm yöntemleri engelleyen bir basit oluşturun :

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

}

Bu önleme yay yapılandırma dosyasında kullanılır:

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

Umarım yardımcı olur!


6

Aslen PrimeFaces forumunda gönderildi @ http://forum.primefaces.org/viewtopic.php?f=3&t=29546

Son zamanlarda, uygulamamın performansını değerlendirmeyi, JPA sorgularını ayarlamayı, dinamik SQL sorgularını adlandırılmış sorgularla değiştirmeyi takıntı haline getirdim ve sadece bu sabah, bir alıcı yönteminin Java Visual VM'de diğerlerinden daha sıcak bir SPOT olduğunu fark ettim. kodum (veya kodumun çoğunluğu).

Getter yöntemi:

PageNavigationController.getGmapsAutoComplete()

Ui tarafından referans verildi: index.xhtml dosyasına ekleyin

Aşağıda, PageNavigationController.getGmapsAutoComplete () öğesinin Java Visual VM'de bir HOT SPOT (performans sorunu) olduğunu göreceksiniz. Daha aşağı bakarsanız, ekran yakalamada, PrimeFaces tembel veritabl alıcı yöntemi olan getLazyModel () 'in, sadece enduser çok fazla' tembel veritabl 'tür şeyler / işlemler / görevler yaptığında sıcak bir nokta olduğunu göreceksiniz. uygulamasında. :)

Java Visual VM: HOT SPOT gösteriliyor

Aşağıdaki (orijinal) koda bakınız.

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

İndex.xhtml dosyasında aşağıdakiler tarafından başvurulmuştur:

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

Çözüm: Bu bir 'getter' yöntemi olduğundan, kodu taşıyın ve yöntem çağrılmadan önce gmapsAutoComplete öğesine değer atayın; aşağıdaki koda bakınız.

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

Test sonuçları: PageNavigationController.getGmapsAutoComplete () artık Java Visual VM'de bir HOT SPOT değil (artık görünmüyor)

Uzman kullanıcıların birçoğu genç JSF geliştiricilerine 'getter' yöntemlerinde kod eklememelerini tavsiye ettiğinden bu konuyu paylaşıyoruz. :)


4

CDI kullanıyorsanız, Producers yöntemlerini kullanabilirsiniz. Birçok kez çağrılır, ancak ilk çağrı sonucu fasulye kapsamında önbelleğe alınır ve ağır nesneleri hesaplayan veya başlatan alıcılar için etkilidir! Daha fazla bilgi için buraya bakın .


3

AOP'yi, alıcılarımızın sonuçlarını yapılandırılabilir bir süre için önbelleğe alan bir tür Unsur oluşturmak için kullanabilirsiniz. Bu, düzinelerce erişimcide kaynak plakasını kopyalayıp yapıştırmanıza gerek kalmasını önler.


Bahsettiğiniz bu Bahar AOP'si mi? Aspects ile ilgili bir kod pasajını veya iki kodunu nerede bulabileceğimi biliyor musunuz? Bahar dokümantasyonunun 6. bölümünün tamamını okumak, Bahar kullanmıyorum gibi aşırıya kaçmış gibi görünüyor;)
Sevas

-1

SomeProperty'nin değerini hesaplamak pahalıysa, bu muhtemelen bir sorun olabilir.

Buna erken optimizasyon diyoruz. Bir profil oluşturucunun size bir mülkün hesaplanmasının çok pahalı olduğunu söylemesi, onu bir kereden çok üç kez çağırmanın önemli bir performans etkisi olduğunu, açıkladığınız şekilde önbelleğe almayı eklersiniz. Ancak, primerleri çarpanlara ayırma veya bir alıcıda bir veritabanına erişme gibi gerçekten aptalca bir şey yapmadığınız sürece, kodunuzun muhtemelen hiç düşünmediğiniz yerlerde bir düzine daha kötü verimsizliği vardır.


Bu nedenle soru - someProperty hesaplamak için pahalı bir şeye karşılık gelirse (veya bir veritabanına veya faktoring primerlerine erişirken koyduğunuzda), istek başına birkaç kez hesaplama yapmaktan kaçınmanın en iyi yolu nedir ve soruda listelediğim çözüm en iyisi. Soruyu cevaplamıyorsanız, yorum göndermek için iyi bir yer, değil mi? Ayrıca, yayınınız BalusC'un yazısıyla ilgili yorumunuzla çelişiyor gibi görünüyor - yorumlarda anında hesaplamalar yapmanın iyi olduğunu söylüyorsunuz ve yazınızda bunun aptal olduğunu söylüyorsunuz. Çizgiyi nerede çizdiğinizi sorabilir miyim?
Sevas

Bu kayan bir ölçek, siyah beyaz bir sorun değil. Bazı şeyler açıkça bir sorun değildir, örneğin birkaç değer eklemek, çünkü saniyenin milyonda birinden daha azını alırlar ( çok daha az, aslında). Bazıları açıkça DB veya dosya erişimi gibi bir sorundur, çünkü 10ms veya daha uzun sürebilirler - ve kesinlikle bunları bilmeniz gerekir, böylece sadece alıcılarda değil, mümkünse bunlardan kaçınabilirsiniz. Ama her şey için, çizgi profillerin size söylediği yerdir.
Michael Borgwardt

-1

Ben de stok JSF yerine bu gibi Çerçeve kullanarak Primefaces tavsiye, JSF ekibi e önce bu sorunları ele. primefaces'de g kısmi gönderim ayarlayabilirsiniz. Aksi takdirde BalusC bunu iyi açıkladı.


-2

JSF'de hala büyük bir sorun. Örneğin isPermittedToBlaBla, güvenlik kontrolleri için bir yönteminiz varsa ve sizin görüşünüze göre rendered="#{bean.isPermittedToBlaBla}yöntem birden çok kez çağrılır.

Güvenlik kontrolü karmaşık olabilir, örn. LDAP sorgusu vb.

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

ve her oturum için bunu istek başına sağlamanız gerekir.

Ich düşünüyorum birden çok çağrı önlemek için JSF burada bazı uzantıları uygulamak gerekir (örneğin ek açıklama aşamadan @Phase(RENDER_RESPONSE)sonra sadece bir kez bu yöntemi calle RENDER_RESPONSE...)


2
Sonucu RequestParameterMap
Christophe Roussy
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.