webentwicklung-frage-antwort-db.com.de

Warum JSF Getter mehrfach aufruft

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

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

Wenn ich beim Aufrufen des Getter für someProperty eine Protokollnachricht drucke 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 der Fall) ):

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 der Wert von someProperty teuer zu berechnen ist, kann dies möglicherweise ein Problem sein.

Ich habe ein bisschen gegoogelt und gedacht, dass dies ein bekanntes Problem ist. Eine Problemumgehung bestand darin, eine Überprüfung einzuschließen und festzustellen, ob diese 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 Code für das Boilerplate 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 unnötigen Code zu erreichen? Gibt es eine Möglichkeit, JSF davon abzuhalten, sich auf diese Weise zu verhalten?

Danke für deinen Beitrag!

251
Sevas

Dies wird durch die Art der verzögerten Ausdrücke _#{}_ verursacht (beachten Sie, dass sich "ältere" Standardausdrücke _${}_ genauso verhalten, wenn Facelets anstelle von JSP verwendet werden). Der zurückgestellte Ausdruck wird nicht sofort ausgewertet, sondern als ValueExpression Objekt erstellt, und die Getter-Methode hinter dem Ausdruck wird jedes Mal ausgeführt, wenn der Code aufgerufen wird ValueExpression#getValue() .

Dies wird normalerweise ein- oder zweimal pro JSF-Anforderungs-/Antwortzyklus aufgerufen, je nachdem, ob es sich bei der Komponente um eine Eingabe- oder eine Ausgabekomponente handelt ( erfahren Sie es hier ). Diese Anzahl kann jedoch (viel) höher sein, wenn sie in iterierenden JSF-Komponenten (wie _<h:dataTable>_ und _<ui:repeat>_) oder hier und da in einem booleschen Ausdruck wie dem rendered verwendet wird. Attribut. JSF (insbesondere EL) speichert das ausgewertete Ergebnis des EL-Ausdrucks überhaupt nicht im Cache, da es may bei jedem Aufruf unterschiedliche Werte zurückgibt (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 keine Gedanken machen. Die Story ändert sich jedoch aus irgendeinem Grund, wenn Sie teure DB/Business-Logik in der Getter-Methode ausführen. Dies würde jedes Mal neu ausgeführt werden!

Getter-Methoden in JSF-Backing-Beans sollten so entworfen werden, dass sie ausschließlich die bereits vorbereitete Eigenschaft zurückgeben und nicht mehr, genau wie in angegeben). Javabeans-Spezifikation . Sie sollten überhaupt keine teure DB/Business-Logik ausführen. Hierzu sollten die Listener-Methoden _@PostConstruct_ und/oder (Action) verwendet werden. Sie werden nur einmal ausgeführt , und das ist genau das, was Sie wollen.

Hier ist eine Zusammenfassung aller right verschiedenen Möglichkeiten, eine Eigenschaft voreinzustellen/zu laden.

_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 nicht den Bean-Konstruktor oder den Initialisierungsblock für den Job verwenden sollten , da dieser möglicherweise mehrmals aufgerufen wird, wenn Sie ein Bean-Management-Framework verwenden, das verwendet Proxies wie CDI.

Wenn es für Sie aufgrund einiger restriktiver Entwurfsanforderungen wirklich keine anderen Möglichkeiten gibt, sollten Sie das verzögerte Laden innerhalb der Getter-Methode einführen. Das heißt Wenn die Eigenschaft null ist, 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ötigerweise bei jedem einzelnen Getter-Aufruf ausgeführt.

Siehe auch:

333
BalusC

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:view - Tag einschließen

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

      .. jsf page here...

<f:view>
16
César Alforde

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

Ich erstelle ein einfaches MethodInterceptor, das alle mit einer speziellen Anmerkung versehenen Methoden abfängt:

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 Abfangjäger 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!

9
Nicolas Labrot

Ursprünglich gepostet im PrimeFaces-Forum @ 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 als der Rest meines Codes (oder die Mehrheit meines Codes).

Getter-Methode:

PageNavigationController.getGmapsAutoComplete()

Referenziert von ui: include in in index.xhtml

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

Java Visual VM: showing HOT SPOT

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 dies eine Getter-Methode ist, verschieben Sie den Code und weisen Sie gmapsAutoComplete einen Wert zu, bevor die Methode aufgerufen wird. 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)

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

6
Howard

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

4
Heidarzadeh

Sie könnten wahrscheinlich AOP verwenden, um eine Art Aspekt zu erstellen, der die Ergebnisse unserer Getter für eine konfigurierbare Zeitspanne zwischengespeichert. Dies würde Sie daran hindern, Boilerplate-Code in Dutzenden von Accessoren zu kopieren und einzufügen.

3
matt b

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

Die Sicherheitsüberprüfung könnte z.B. LDAP-Abfrage usw. Das müssen Sie 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 Mehrfachaufrufe zu vermeiden (z. B. Annotation @Phase(RENDER_RESPONSE) diese Methode nur einmal nach der Phase RENDER_RESPONSE Aufrufen ...)

0
Morad