Warum JSF Getter mehrmals aufruft

256

Angenommen, ich gebe eine outputText-Komponente wie folgt an:

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

Wenn ich eine Protokollnachricht drucke, wenn der Getter für somePropertyaufgerufen wird, und die Seite lade, ist es trivial zu bemerken, dass der Getter mehr als einmal pro Anforderung aufgerufen wird (in meinem Fall ist dies zwei- oder dreimal passiert):

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

Wenn die somePropertyBerechnung des Werts von teuer ist, kann dies möglicherweise ein Problem sein.

Ich habe ein bisschen gegoogelt und festgestellt, dass dies ein bekanntes Problem ist. Eine Problemumgehung bestand darin, eine Überprüfung einzuschließen und festzustellen, ob sie bereits berechnet wurde:

private String someProperty;

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

Das Hauptproblem dabei ist, dass Sie eine Menge Boilerplate-Code erhalten, ganz zu schweigen von privaten Variablen, die Sie möglicherweise nicht benötigen.

Was sind die Alternativen zu diesem Ansatz? Gibt es eine Möglichkeit, dies ohne so viel unnötigen Code zu erreichen? Gibt es eine Möglichkeit, JSF daran zu hindern, sich auf diese Weise zu verhalten?

Danke für deinen Beitrag!

Sevas
quelle

Antworten:

340

Dies wird durch die Art der verzögerten Ausdrücke verursacht #{}(beachten Sie, dass sich "Legacy" -Standardausdrücke ${}genau gleich verhalten, wenn Facelets anstelle von JSP verwendet wird). Der verzögerte Ausdruck wird nicht sofort ausgewertet, sondern als ValueExpressionObjekt erstellt, und die Getter-Methode hinter dem Ausdruck wird jedes Mal ausgeführt, wenn der Code aufgerufen wirdValueExpression#getValue() .

Dies wird normalerweise ein- oder zweimal pro JSF-Anforderungs- / Antwortzyklus aufgerufen, je nachdem, ob die Komponente eine Eingabe- oder Ausgabekomponente ist ( lernen Sie es hier ). Diese Anzahl kann jedoch (viel) höher sein, wenn sie beim Iterieren von JSF-Komponenten (wie <h:dataTable>und <ui:repeat>) oder hier und da in einem booleschen Ausdruck wie dem renderedAttribut verwendet wird. JSF (insbesondere EL) speichert das ausgewertete Ergebnis des EL-Ausdrucks überhaupt nicht zwischen, da es bei jedem Aufruf unterschiedliche Werte zurückgeben kann (z. B. wenn es von der aktuell iterierten datierbaren Zeile abhängt).

Das Auswerten eines EL-Ausdrucks und das Aufrufen einer Getter-Methode ist eine sehr kostengünstige Operation, daher sollten Sie sich darüber im Allgemeinen überhaupt keine Gedanken machen. Die Story ändert sich jedoch, wenn Sie aus irgendeinem Grund teure DB- / Geschäftslogik in der Getter-Methode ausführen. Dies würde jedes Mal neu ausgeführt!

Getter - Methoden in JSF Träger Bohnen sollten auf diese Weise gestaltet werden , dass sie nur zurückgeben , die bereits vorbereitete Eigenschaft und nichts mehr, genau wie pro die Javabeans - Spezifikation . Sie sollten überhaupt keine teure DB- / Geschäftslogik ausführen. Dazu @PostConstructsollten die Listener-Methoden der Bean und / oder (Aktion) verwendet werden. Sie werden zu einem bestimmten Zeitpunkt des anforderungsbasierten JSF-Lebenszyklus nur einmal ausgeführt , und genau das möchten Sie.

Hier finden Sie eine Zusammenfassung aller verschiedenen richtigen Methoden zum Voreinstellen / Laden einer Eigenschaft.

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

}

Beachten Sie, dass Sie den Bean-Konstruktor oder den Initialisierungsblock für den Job nicht verwenden sollten, da er möglicherweise mehrmals aufgerufen wird, wenn Sie ein Bean-Verwaltungsframework verwenden, das Proxys wie CDI verwendet.

Wenn es für Sie aufgrund einiger restriktiver Designanforderungen wirklich keine anderen Möglichkeiten gibt, sollten Sie das verzögerte Laden in die Getter-Methode einführen. Das heißt, wenn die Eigenschaft ist null, dann laden Sie sie und weisen Sie sie der Eigenschaft zu, andernfalls geben Sie sie zurück.

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

        return someProperty;
    }

Auf diese Weise wird die teure DB / Business-Logik nicht unnötig bei jedem einzelnen Getter-Aufruf ausgeführt.

Siehe auch:

BalusC
quelle
5
Verwenden Sie Getter nur nicht, um Geschäftslogik zu erstellen. Das ist alles. Ordnen Sie Ihre Codelogik neu an. Meine Wette, dass es bereits behoben ist, indem nur der Konstruktor, das Postkonstrukt oder die Aktionsmethode auf intelligente Weise verwendet werden.
BalusC
3
-1, stimme überhaupt nicht zu. Der gesamte Sinn der javaBeans-Spezifikation besteht darin, zuzulassen , dass Eigenschaften mehr als nur ein Feldwert sind , und "abgeleitete Eigenschaften", die im laufenden Betrieb berechnet werden, sind völlig normal. Die Sorge um redundante Getter-Aufrufe ist nichts anderes als vorzeitige Optimierung.
Michael Borgwardt
3
Erwarten Sie, ob sie mehr tun als nur Daten zurückzugeben, wie Sie selbst so deutlich angegeben haben :)
BalusC
4
Sie könnten hinzufügen, dass die verzögerte Initialisierung in Getter in JSF noch gültig ist :)
Bozho
2
@ Harry: Es wird das Verhalten nicht ändern. Sie können jedoch jede Geschäftslogik im Getter bedingt verarbeiten, indem Sie das Laden verzögern und / oder die aktuelle Phasen-ID überprüfen FacesContext#getCurrentPhaseId().
BalusC
17

Mit JSF 2.0 können Sie einen Listener an ein Systemereignis anhängen

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

Alternativ können Sie die JSF-Seite in ein f:viewTag einschließen

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

      .. jsf page here...

<f:view>
César Alforde
quelle
9

Ich habe einen Artikel darüber geschrieben, wie man JSF-Beans-Getter mit Spring AOP zwischenspeichert.

Ich erstelle eine einfache, MethodInterceptordie alle Methoden abfängt, die mit einer speziellen Anmerkung versehen sind:

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

}

Dieser Interceptor wird in einer Federkonfigurationsdatei verwendet:

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

Hoffe es wird helfen!

Nicolas Labrot
quelle
6

Ursprünglich veröffentlicht im PrimeFaces-Forum unter http://forum.primefaces.org/viewtopic.php?f=3&t=29546

Vor kurzem war ich besessen davon, die Leistung meiner App zu bewerten, JPA-Abfragen zu optimieren, dynamische SQL-Abfragen durch benannte Abfragen zu ersetzen, und erst heute Morgen erkannte ich, dass eine Getter-Methode in Java Visual VM eher ein HOT SPOT war als der Rest von mein Code (oder der Großteil meines Codes).

Getter-Methode:

PageNavigationController.getGmapsAutoComplete()

Referenziert von ui: in in index.xhtml aufnehmen

Unten sehen Sie, dass PageNavigationController.getGmapsAutoComplete () ein HOT SPOT (Leistungsproblem) in Java Visual VM ist. Wenn Sie weiter unten auf dem Bildschirm nachsehen, werden Sie feststellen, dass getLazyModel (), die Lazy Datatable Getter-Methode von PrimeFaces, ebenfalls ein Hot Spot ist, nur wenn der Endbenutzer viele "Lazy Datatable" -Typen / Operationen / Aufgaben ausführt in der App. :) :)

Java Visual VM: Zeigt HOT SPOT an

Siehe (Original-) Code unten.

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

In index.xhtml wird auf Folgendes verwiesen:

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

Lösung: Da es sich um eine Getter-Methode handelt, verschieben Sie den Code und weisen Sie gmapsAutoComplete einen Wert zu, bevor Sie die Methode aufrufen. siehe Code unten.

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

Testergebnisse: PageNavigationController.getGmapsAutoComplete () ist in Java Visual VM kein HOT SPOT mehr (wird nicht einmal mehr angezeigt)

Teilen Sie dieses Thema, da viele der erfahrenen Benutzer Junior-JSF-Entwicklern geraten haben, KEINEN Code in 'Getter'-Methoden hinzuzufügen. :) :)

Howard
quelle
4

Wenn Sie CDI verwenden, können Sie Producers-Methoden verwenden. Es wird viele Male aufgerufen, aber das Ergebnis des ersten Aufrufs wird im Bereich der Bean zwischengespeichert und ist effizient für Getter, die schwere Objekte berechnen oder initialisieren! Sehen Sie hier , für weitere Informationen.

Heidarzadeh
quelle
3

Sie könnten wahrscheinlich AOP verwenden, um eine Art Aspekt zu erstellen, der die Ergebnisse unserer Getter für einen konfigurierbaren Zeitraum zwischengespeichert. Dies würde verhindern, dass Sie Boilerplate-Code in Dutzenden von Accessoren kopieren und einfügen müssen.

matt b
quelle
Ist das Frühling AOP, von dem Sie sprechen? Würdest du wissen, wo ich ein oder zwei Codefragmente finden könnte, die sich mit Aspekten befassen? Das Lesen des gesamten 6. Kapitels der Spring-Dokumentation scheint übertrieben, da ich Spring nicht benutze;)
Sevas
-1

Wenn die Berechnung des Werts von someProperty teuer ist, kann dies möglicherweise ein Problem sein.

Dies nennen wir eine vorzeitige Optimierung. In dem seltenen Fall, dass ein Profiler Ihnen mitteilt, dass die Berechnung einer Eigenschaft so außerordentlich teuer ist, dass ein dreimaliger und kein einmaliger Aufruf erhebliche Auswirkungen auf die Leistung hat, fügen Sie wie beschrieben Caching hinzu. Aber wenn Sie nicht etwas wirklich Dummes tun, wie das Faktorisieren von Primzahlen oder den Zugriff auf eine Datenbank in einem Getter, weist Ihr Code höchstwahrscheinlich ein Dutzend schlimmerer Ineffizienzen an Stellen auf, an die Sie noch nie gedacht haben.

Michael Borgwardt
quelle
Daher die Frage: Wenn someProperty einer teuren Berechnung entspricht (oder wie Sie sagen, auf eine Datenbank zuzugreifen oder Primzahlen zu faktorisieren), was ist der beste Weg, um die Berechnung mehrmals pro Anfrage zu vermeiden, und ist die Lösung, die ich in der Frage aufgeführt habe Beste. Wenn Sie die Frage nicht beantworten, sind Kommentare ein guter Ort zum Posten, nein? Außerdem scheint Ihr Beitrag Ihrem Kommentar zu BalusCs Beitrag zu widersprechen - in den Kommentaren sagen Sie, dass es in Ordnung ist, Berechnungen im laufenden Betrieb durchzuführen, und in Ihrem Beitrag sagen Sie, dass es dumm ist. Kann ich fragen, wo Sie die Grenze ziehen?
Sevas
Es ist eine gleitende Skala, kein Schwarz-Weiß-Problem. Einige Dinge sind eindeutig kein Problem, z. B. das Hinzufügen einiger Werte, da sie weniger als eine Millionstel Sekunde dauern ( eigentlich viel weniger). Einige sind eindeutig ein Problem, wie z. B. DB- oder Dateizugriff, da sie 10 ms oder länger dauern können - und Sie müssen diese unbedingt kennen, damit Sie sie nach Möglichkeit vermeiden können, nicht nur in Gettern. Aber für alles andere ist die Zeile dort, wo der Profiler es Ihnen sagt.
Michael Borgwardt
-1

Ich würde auch raten, ein solches Framework wie Primefaces anstelle von JSF zu verwenden. Sie behandeln solche Probleme vor dem JSF-Team e. g In Primefaces können Sie die teilweise Übermittlung festlegen. Ansonsten hat BalusC es gut erklärt.

Martin Karari
quelle
-2

Es ist immer noch ein großes Problem in JSF. Zum Beispiel, wenn Sie eine Methode habenisPermittedToBlaBla für Sicherheitsüberprüfungen haben und Ihrer Ansicht nach habenrendered="#{bean.isPermittedToBlaBla} wird die Methode mehrmals aufgerufen.

Die Sicherheitskontrolle könnte z. LDAP-Abfrage usw. Sie müssen dies also mit vermeiden

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

und Sie müssen dies innerhalb einer Session Bean pro Anfrage sicherstellen.

Ich denke, JSF muss hier einige Erweiterungen implementieren, um mehrere Aufrufe zu vermeiden (z. B. Annotation ruft @Phase(RENDER_RESPONSE)diese Methode nur einmal nach der RENDER_RESPONSEPhase auf ...)

Morad
quelle
2
Sie könnten das Ergebnis in der RequestParameterMap
Christophe Roussy