270 votes

Pourquoi JSF appelle les getters plusieurs fois

Disons que je spécifie un composant outputText comme ceci :

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

Si j'imprime un message de journal lorsque le getter pour someProperty est appelé et que la page est chargée, il est trivial de remarquer que le getter est appelé plus d'une fois par requête (deux ou trois fois, c'est ce qui s'est passé dans mon cas) :

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

Si la valeur de someProperty est coûteux à calculer, cela peut potentiellement poser un problème.

J'ai fait quelques recherches sur Internet et je me suis dit que c'était un problème connu. Une solution de contournement consistait à inclure une vérification pour voir si le calcul avait déjà été effectué :

private String someProperty;

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

Le principal problème est qu'il en résulte une multitude de codes passe-partout, sans parler des variables privées dont vous pourriez ne pas avoir besoin.

Quelles sont les alternatives à cette approche ? Existe-t-il un moyen d'y parvenir sans autant de code inutile ? Existe-t-il un moyen d'empêcher JSF de se comporter de cette manière ?

Merci pour votre contribution !

357voto

BalusC Points 498232

Ceci est dû à la nature des expressions différées #{} (notez que les expressions standard "anciennes" ${} se comportent exactement de la même manière lorsque Facelets est utilisé au lieu de JSP). L'expression différée n'est pas immédiatement évaluée, mais créée en tant que ValueExpression et la méthode getter derrière l'expression est exécutée à chaque fois que le code appelle ValueExpression#getValue() .

Il sera normalement invoqué une ou deux fois par cycle demande-réponse JSF, selon que le composant est un composant d'entrée ou de sortie ( Apprenez-le ici ). Toutefois, ce nombre peut être (beaucoup) plus élevé lorsqu'il est utilisé dans des composants JSF itératifs (tels que <h:dataTable> y <ui:repeat> ), ou ici et là dans une expression booléenne comme l'expression rendered attribut. JSF (plus précisément, EL) ne mettra pas du tout en cache le résultat de l'évaluation de l'expression EL puisqu'il mai renvoie des valeurs différentes à chaque appel (par exemple, lorsqu'elle dépend de la ligne du tableau de données en cours d'itération).

L'évaluation d'une expression EL et l'appel d'une méthode getter sont des opérations très peu coûteuses, de sorte que vous ne devriez généralement pas vous en préoccuper. Cependant, l'histoire change lorsque, pour une raison ou une autre, vous exécutez une logique commerciale ou de base de données coûteuse dans la méthode getter. Cette opération sera réexécutée à chaque fois !

Les méthodes Getter dans les backing beans JSF doivent être conçues de manière à ce qu'elles ne puissent que retourner la propriété déjà préparée et rien de plus, exactement comme dans l'application Spécification Javabeans . Ils ne devraient pas faire de logique de base de données/logique commerciale coûteuse. Pour cela, la fonction @PostConstruct et/ou des méthodes d'écoute (d'action) doivent être utilisées. Elles sont exécutées une seule fois à un moment donné du cycle de vie de JSF basé sur les requêtes et c'est exactement ce que vous voulez.

Voici un résumé des différentes droite les moyens de prérégler/charger un bien.

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

}

Notez que vous devez no utilisez le constructeur ou le bloc d'initialisation du bean pour cette tâche car il peut être invoqué plusieurs fois si vous utilisez un cadre de gestion des beans qui utilise des proxies, comme CDI.

S'il n'y a pas d'autres moyens pour vous, en raison de certaines exigences de conception restrictives, alors vous devez introduire le chargement paresseux dans la méthode getter. C'est-à-dire que si la propriété est null puis le charger et l'affecter à la propriété, sinon le retourner.

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

        return someProperty;
    }

De cette façon, la logique coûteuse de la base de données et de l'entreprise ne sera pas exécutée inutilement à chaque appel au getter.

Voir aussi :

0 votes

Vous avez tout à fait raison de dire que la logique d'entreprise ne doit pas être exécutée dans des getters et setters, mais j'en suis conscient. Ce que je veux savoir, c'est comment éviter cela en utilisant aussi peu de code passe-partout que possible et s'il existe quelque chose de disponible pour résoudre ce problème.

5 votes

N'utilisez pas les getters pour faire de la logique commerciale. C'est tout. Réorganisez votre code logique. Je parie que c'est déjà réglé en utilisant le constructeur, la postconstruction ou la méthode d'action de manière intelligente.

3 votes

-1, pas du tout d'accord. Le point entier de la spécification javaBeans est de permettre pour être plus qu'une simple valeur de champ, et les "propriétés dérivées" qui sont calculées à la volée sont parfaitement normales. S'inquiéter des appels redondants de getter n'est rien d'autre qu'une optimisation prématurée.

18voto

César Alforde Points 594

Avec JSF 2.0, vous pouvez attacher un écouteur à un événement système.

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

Vous pouvez également inclure la page JSF dans un fichier de type f:view étiquette

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

      .. jsf page here...

<f:view>

9voto

Nicolas Labrot Points 151

J'ai écrit un article sur comment mettre en cache les beans JSF avec Spring AOP.

Je crée un simple MethodInterceptor qui intercepte toutes les méthodes annotées avec une annotation spéciale :

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

}

Cet intercepteur est utilisé dans un fichier de configuration de Spring :

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

J'espère que cela vous aidera !

6voto

Howard Points 354

Publié à l'origine dans le forum PrimeFaces @ http://forum.primefaces.org/viewtopic.php?f=3&t=29546

Récemment, j'ai été obsédé par l'évaluation des performances de mon application, le réglage des requêtes JPA, le remplacement des requêtes SQL dynamiques par des requêtes nommées, et ce matin, j'ai constaté qu'une méthode getter était plus un HOT SPOT dans Java Visual VM que le reste de mon code (ou la majorité de mon code).

Méthode Getter :

PageNavigationController.getGmapsAutoComplete()

Référencé par ui:include dans dans index.xhtml

Ci-dessous, vous verrez que PageNavigationController.getGmapsAutoComplete() est un HOT SPOT (problème de performance) dans Java Visual VM. Si vous regardez plus bas, sur la capture d'écran, vous verrez que getLazyModel(), la méthode PrimeFaces lazy datatable getter, est aussi un point chaud, seulement quand l'utilisateur final fait beaucoup de choses/opérations/tâches de type 'lazy datatable' dans l'application :)

Java Visual VM: showing HOT SPOT

Voir le code (original) ci-dessous.

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

Référencé par ce qui suit dans index.xhtml :

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

Solution : comme il s'agit d'une méthode 'getter', déplacez le code et attribuez une valeur à gmapsAutoComplete avant que la méthode ne soit appelée ; voir le code ci-dessous.

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

Résultats des tests : PageNavigationController.getGmapsAutoComplete() n'est plus un HOT SPOT dans Java Visual VM (ne s'affiche même plus).

Je partage ce sujet, car de nombreux utilisateurs experts ont conseillé aux jeunes développeurs JSF de ne PAS ajouter de code dans les méthodes 'getter' :)

3voto

matt b Points 73770

Vous pourriez probablement utiliser la POA pour créer une sorte d'Aspect qui mettrait en cache les résultats de nos getters pendant une durée configurable. Cela vous éviterait d'avoir à copier-coller du code passe-partout dans des dizaines d'accesseurs.

0 votes

C'est bien de Spring AOP dont vous parlez ? Savez-vous où je pourrais trouver un ou deux extraits de code traitant des aspects ? Lire l'ensemble du 6ème chapitre de la documentation de Spring me semble excessif car je n'utilise pas Spring ;)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X