Pourquoi JSF appelle plusieurs fois getters

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

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

si j'imprime un message de log lorsque le getter pour someProperty est appelé et charge la page, il est trivial de noter que le getter est appelé plus d'une fois par requête (deux ou trois fois 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ûteuse à calculer, cela peut poser un problème.

I googlé un peu et pensé que c'est un problème connu. Une solution était d'inclure une vérification et de voir si elle avait déjà été calculée:

private String someProperty;

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

le problème principal avec cela est que vous obtenez des charges de code boilerplate, sans parler des variables privées que vous pourriez ne pas avoir besoin.

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

Merci pour votre contribution!

241
demandé sur BalusC 2010-01-19 02:43:39
la source

8 ответов

ceci est causé par la nature des expressions différées #{} (notez que les expressions standard ${} se comportent exactement de la même façon lorsque les Facelets sont utilisés à la place de JSP). L'expression différée n'est pas immédiatement évalué, mais créé comme un ValueExpression objet et la méthode getter derrière l'expression est exécutée chaque fois que le code appelle ValueExpression#getValue() .

cela sera normalement invoqué une ou deux fois par cycle requête-réponse JSF, selon que le composant est un composant d'entrée ou de sortie ( learn it here ). Cependant, ce nombre peut monter (beaucoup) plus haut lorsqu'il est utilisé dans les composants itératifs du JSF (tels que <h:dataTable> et <ui:repeat> ), ou ici et là dans une expression booléenne comme l'attribut rendered . JSF (spécifiquement, EL) ne cache pas le résultat évalué de L'expression EL car il peut retour des valeurs différentes à chaque appel (par exemple, lorsqu'elle est dépendante de l'itération en ligne de datatable).

évaluer une expression EL et invoquer une méthode getter est une opération très bon marché, donc vous ne devriez généralement pas vous soucier de cela du tout. Cependant, l'histoire change quand vous effectuez DB/business logique coûteuse dans la méthode getter pour une certaine raison. Ce serait ré-exécuté à chaque fois!

Les méthodes Getter dans les fèves de soutien JSF doivent être conçues de cette façon qu'ILS uniquement retourner la propriété déjà préparée et rien de plus, exactement comme selon la spécification Javabeans . Ils ne devraient pas faire de logique coûteuse DB/business du tout. Pour ce faire, il convient d'utiliser les méthodes d'écoute @PostConstruct et/ou (action)du haricot. Ils sont exécutés une seule fois à un moment donné du cycle de vie du JSF basé sur la demande et c'est exactement ce que vous voulez.

voici un résumé de tous les différents droit façons de présélectionner/charger une propriété.

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 ne devez pas utiliser le constructeur de bean ou le bloc d'initialisation pour le travail parce qu'il peut être invoqué plusieurs fois si vous utilisez un cadre de gestion de bean qui utilise des mandataires, comme CDI.

S'il ya pour vous vraiment pas d'autres moyens, en raison d'une certaine conception restrictive exigences, alors vous devriez introduire chargement paresseux à l'intérieur de la méthode getter. C'est-à-dire: si la propriété est null , puis Charger et attribuer à la propriété, sinon 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 coûteuse logique DB/business ne sera pas inutilement exécutée sur chaque appel getter.

voir aussi:

319
répondu BalusC 2017-05-23 13:31:12
la source

avec JSF 2.0 vous pouvez attacher un auditeur à un événement système

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

vous pouvez aussi inclure la page JSF dans une étiquette f:view 151940920"

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

      .. jsf page here...

<f:view>
14
répondu César Alforde 2010-10-31 04:09:08
la source

j'ai écrit un article sur la façon de mettre en cache getter haricots JSF avec ressort 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 à ressort:

    <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 ça aidera!

6
répondu Nicolas Labrot 2011-01-05 17:13:23
la source

publié à L'origine dans PrimeFaces forum @ 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 reconnu qu'une méthode getter était plus un point chaud dans la VM visuelle Java que le reste de mon code (ou la majorité de mon code).

méthode Getter:

PageNavigationController.getGmapsAutoComplete()

référencé par l'ui: inclure dans l'index.XHTML

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

Java Visual VM: showing HOT SPOT

voir 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 l'index.xhtml:

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

Solution: puisqu'il s'agit d'une méthode' getter', déplacez le code et assignez une valeur à gmapautocomplete avant que la méthode ne soit appelée; voir 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 point chaud en Java Visual VM (ne s'affiche même pas plus)

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

4
répondu Howard 2013-04-14 17:32:03
la source

vous pourriez probablement utiliser AOP pour créer une sorte d'Aspect qui cache les résultats de nos getters pour une durée configurable. Cela vous éviterait d'avoir besoin de copier-coller du code boilerplate dans des douzaines d'accesseurs.

2
répondu matt b 2010-01-19 02:45:53
la source

si vous utilisez le CDI, Vous pouvez utiliser les méthodes des producteurs. Il sera appelé plusieurs fois, mais le résultat du premier appel est caché dans scope of the bean et est efficace pour les getters qui calculent ou initialisent des objets lourds! Voir ici , pour plus d'informations.

2
répondu Heidarzadeh 2017-05-23 15:34:27
la source

c'est encore un gros problème à JSF. Par exemple, si vous avez une méthode isPermittedToBlaBla pour les contrôles de sécurité et dans votre avis vous avez rendered="#{bean.isPermittedToBlaBla} alors la méthode sera appelée plusieurs fois.

le contrôle de sécurité peut être compliqué E. G. Requête LDAP etc. Vous devez donc éviter cela avec

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

et vous devez vous assurer que dans une session bean ce Par demande.

Ich pense JSF doit mettre en œuvre ici quelques extensions à évitez les appels multiples(E. g annotation @Phase(RENDER_RESPONSE) calle cette méthode une seule fois après la phase RENDER_RESPONSE ...)

0
répondu Morad 2012-01-06 17:42:47
la source

si la valeur de someProperty est coûteux à calculer, cela peut potentiellement être un problème.

C'est ce que nous appelons une optimisation prématurée. Dans le cas rare où un profileur vous dit que le calcul d'une propriété est si extraordinairement coûteux que l'appeler trois fois plutôt qu'une fois a un impact significatif sur les performances, vous ajoutez la mise en cache comme vous le décrivez. Mais à moins que vous ne fassiez quelque chose de vraiment stupide comme factoring primes ou accès à une base de données dans un getter, votre code a très probablement une douzaine de pires inefficacités dans des endroits auxquels vous n'avez jamais pensé.

-1
répondu Michael Borgwardt 2010-01-19 03:39:14
la source

Autres questions sur performance jsf el getter