Dlaczego JSF dzwoni do getterów wielokrotnie

Załóżmy, że podaję komponent outputText w następujący sposób:

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

Jeśli wydrukuję komunikat logu, gdy wywołany jest getter dla someProperty i załaduję stronę, trywialne jest zauważenie, że getter jest wywoływany więcej niż raz na żądanie (w moim przypadku zdarzyło się to dwa lub trzy razy):

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

Jeśli wartość someProperty jest droga do obliczenia, może to stanowić problem.

Trochę wygooglowałem i uznałem, że jest to znany problem. Jednym z obejść było sprawdzenie i zobacz czy już zostało obliczone:

private String someProperty;

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

Głównym problemem jest to, że dostajesz mnóstwo kodu kotła, nie wspominając o prywatnych zmiennych, które mogą nie być potrzebne.

Jakie są alternatywy dla tego podejścia? Czy istnieje sposób, aby to osiągnąć bez tyle niepotrzebnego kodu? Czy jest sposób, aby powstrzymać JSF od zachowania się w ten sposób?

Dzięki za wkład!

Author: BalusC, 2010-01-19

8 answers

Jest to spowodowane charakterem wyrażeń odroczonych #{} (zauważ, że standardowe wyrażenia " legacy "${} zachowują się dokładnie tak samo, gdy Facelets jest używany zamiast JSP). Wyrażenie odroczone nie jest natychmiast oceniane, ale tworzone jako ValueExpression obiekt i metoda getter za wyrażeniem jest wykonywana za każdym razem, gdy kod wywoła ValueExpression#getValue().

Zwykle jest to wywoływane jeden lub dwa razy na cykl odpowiedzi żądania JSF, w zależności od czy komponent jest komponentem wejściowym czy wyjściowym (dowiedz się tego tutaj ). Jednak liczba ta może wzrosnąć (znacznie) wyżej, gdy jest używana w iteracji komponentów JSF (takich jak <h:dataTable> i <ui:repeat>), lub Tu i ówdzie w wyrażeniu logicznym, takim jak atrybut rendered. JSF (w szczególności EL) w ogóle nie buforuje obliczonego wyniku wyrażenia EL, ponieważ Może zwracać różne wartości przy każdym wywołaniu (na przykład, gdy zależy to od aktualnie iteracyjnego wiersza danych).

Ocena wyrażenia EL i wywołanie metody gettera jest bardzo tanią operacją, więc generalnie nie powinieneś się tym martwić. Jednak historia zmienia się, gdy z jakiegoś powodu wykonujesz kosztowną logikę DB/business w metodzie getter. To będzie ponownie wykonywane za każdym razem!

Metody Getter w JSF powinny być zaprojektowane w taki sposób, aby zwracały wyłącznie już przygotowaną właściwość i nic więcej, dokładnie tak jak na specyfikacja Javabeans . Nie powinni robić żadnych drogich DB / logiki biznesowej w ogóle. W tym celu należy zastosować metody bean @PostConstruct i/lub (action). Są one wykonywane tylko raz w pewnym momencie cyklu życia JSF opartego na żądaniach i to jest dokładnie to, czego chcesz.

Oto podsumowanie wszystkich różnych right sposobów presetowania / ładowania właściwości.

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

}

Zauważ, że nie powinieneś używać konstruktora lub bloku inicjalizacyjnego Beana do zadania, ponieważ może być wywoływany wiele razy, jeśli używasz frameworku zarządzania bean, który wykorzystuje proxy, takie jak CDI.

Jeśli naprawdę nie ma innych sposobów, ze względu na pewne restrykcyjne wymagania projektowe, powinieneś wprowadzić leniwe Ładowanie wewnątrz metody getter. Tzn. jeśli właściwość jest null, to Załaduj i przypisz ją do właściwości, w przeciwnym razie zwróć ją.

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

        return someProperty;
    }

W ten sposób kosztowna logika DB / business nie będzie niepotrzebnie wykonywana przy każdym wywołaniu getter.

Zobacz też:

 319
Author: BalusC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:12

W JSF 2.0 możesz dołączyć listener do zdarzenia systemowego

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

Alternatywnie możesz dołączyć stronę JSF do znacznika f:view

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

      .. jsf page here...

<f:view>
 14
Author: César Alforde,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-31 01:09:08

Napisałem Artykuł o tym, jak buforować JSF beans getter za pomocą Spring AOP.

Tworzę prostą MethodInterceptor, która przechwytuje wszystkie metody opatrzone specjalną adnotacją:

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

}

Ten interceptor jest używany w pliku konfiguracyjnym sprężyny:

    <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>
Mam nadzieję, że to pomoże!
 6
Author: Nicolas Labrot,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-05 14:13:23

Originally posted in PrimeFaces forum @ http://forum.primefaces.org/viewtopic.php?f=3&t=29546

Ostatnio miałem obsesję na punkcie oceny wydajności mojej aplikacji, dostrajania zapytań JPA, zastępowania dynamicznych zapytań SQL zapytaniami nazwanymi i właśnie dziś rano zauważyłem, że metoda getter była bardziej gorącym punktem w wizualnej maszynie wirtualnej Java niż reszta mojego kodu (lub większość mojego kodu).

Metoda gettera:

PageNavigationController.getGmapsAutoComplete()

Odwołany przez ui: include in in indeks.xhtml

Poniżej zobaczysz, że PageNavigationController.getGmapsAutoComplete() jest HOT SPOT (problem wydajności) w Java Visual VM. Jeśli spojrzysz dalej w dół, na screenie capture, zobaczysz, że getLazyModel (), metoda PrimeFaces lazy datatable getter, jest również gorącym punktem, tylko wtedy, gdy użytkownik końcowy robi wiele rzeczy/operacji/zadań typu "lazy datatable" w aplikacji. :)

Java Visual VM: wyświetlanie HOT SPOT

Patrz (oryginalny) kod poniżej.

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

Indeks.xhtml:

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

Rozwiązanie: ponieważ jest to metoda' getter', przenieś kod i przypisz wartość do gmapsAutoComplete przed wywołaniem metody; patrz kod poniżej.

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

Wyniki testu: PageNavigationController.getGmapsAutoComplete() nie jest już HOT SPOT w Java Visual VM (nawet się już nie pojawia)

Dzielenie się tym tematem, ponieważ wielu ekspertów doradziło młodszym programistom JSF, aby nie dodawali kodu w metodach 'getter'. :)

 4
Author: Howard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-14 13:32:03

Prawdopodobnie mógłbyś użyć AOP do stworzenia jakiegoś aspektu, który buforował wyniki naszych getterów przez konfigurowalną ilość czasu. Zapobiegłoby to konieczności kopiowania i wklejania kodu kotła w dziesiątkach accesorów.

 2
Author: matt b,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-18 23:45:53

Jeśli używasz CDI, możesz użyć metod producentów. Będzie on wywoływany wiele razy, ale wynik pierwszego wywołania jest buforowany w obszarze bean i jest wydajny dla getterów, które obliczają lub inicjalizują ciężkie obiekty! Zobacz tutaj , aby uzyskać więcej informacji.

 2
Author: Heidarzadeh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:27

To nadal duży problem w JSF. Fo przykład jeśli masz metodę isPermittedToBlaBla do kontroli bezpieczeństwa i w Twoim widoku masz rendered="#{bean.isPermittedToBlaBla}, to metoda będzie wywoływana wiele razy.

Kontrola bezpieczeństwa może być skomplikowana np. Zapytanie LDAP itp. Więc musisz tego unikać z

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

I musisz zapewnić w trakcie sesji to na żądanie.

Ich zdaniem JSF musi zaimplementować tutaj niektóre rozszerzenia, aby uniknąć wielu wywołań (np. adnotacja @Phase(RENDER_RESPONSE) ta metoda tylko raz po RENDER_RESPONSE Faza...)

 0
Author: Morad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-06 13:42:47

Jeśli wartość someProperty jest kosztowne do obliczenia, to może to może być problem.

To nazywamy przedwczesną optymalizacją. W rzadkich przypadkach, gdy profiler mówi ci, że obliczanie właściwości jest tak niezwykle kosztowne, że wywołanie jej trzy razy, a nie raz, ma znaczący wpływ na wydajność, dodajesz buforowanie zgodnie z opisem. Ale chyba, że zrobisz coś naprawdę głupiego, jak faktoring Prime lub dostęp do bazy danych w getter, Twój kod najprawdopodobniej ma tuzin gorszych nieefektywności w miejscach, o których nigdy nie myślałeś.
 -1
Author: Michael Borgwardt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-19 00:39:14