Question

Disons que je précise une composante outputText comme ceci:

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

Si j'imprime un message de journal lorsque le getter pour someProperty est appelé et charger la page, il est trivial de noter que le getter est appelé plus d'une fois par demande (deux ou trois fois est ce qui est arrivé 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 pour le calcul, cela peut potentiellement être un problème.

Je googlé un peu et pensé que ce problème est connu. Une solution a été d'inclure un chèque et voir si elle avait déjà été calculé:

private String someProperty;

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

Le principal problème avec ceci est que vous obtenez des charges de code passe-partout, sans parler des variables privées que vous pourriez ne pas avoir besoin.

Quelles sont les alternatives à cette approche? Est-il possible d'y parvenir sans autant code inutile? Y at-il un moyen d'arrêter JSF de se comporter de cette façon?

Merci pour vos commentaires!

Était-ce utile?

La solution

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

Ce sera normalement appelé une ou deux fois par cycle de demande-réponse JSF, selon que le composant est un composant d'entrée ou de sortie ( en savoir ici ). Cependant, ce nombre peut se lever (beaucoup) plus élevé quand il est utilisé dans itérer composants JSF (tels que <h:dataTable> et <ui:repeat>), ou ici et là dans une expression booléenne comme l'attribut rendered. JSF (plus précisément, EL) ne cache le résultat évalué de l'expression EL du tout comme peut renvoyer des valeurs différentes à chaque appel (par exemple, quand il est dépendant de la ligne datatable actuellement itéré).

L'évaluation d'une expression EL et l'invocation d'une méthode de lecture est une opération très pas cher, vous devriez donc généralement pas se soucier du tout. Cependant, l'histoire change lorsque vous effectuez cher la logique DB / d'affaires dans le getter pour une raison quelconque. Ce serait à nouveau exécutée à chaque fois!

getter dans les fèves de support JSF doivent être conçus de cette façon qu'ils uniquement retour la propriété et rien déjà préparé plus, exactement comme par spécification Javabeans . Ils ne devraient pas faire toute logique DB / d'affaires cher du tout. Pour que la @PostConstruct de haricot et / ou (action) méthodes d'écoute doivent être utilisés. Ils sont exécutés une seule fois à un moment donné du cycle de vie JSF basé sur la requête et c'est exactement ce que vous voulez.

Voici un résumé de tous les différents droit moyens de présélection / 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 devez pas utilisation constructeur de haricot ou le bloc d'initialisation pour le travail car il peut être invoqué plusieurs fois si vous utilisez un cadre de gestion de haricot qui utilise des procurations, comme le CDI.

S'il y a pour vous vraiment pas d'autre moyen, en raison de certaines exigences de conception restrictives, alors vous devriez introduire le chargement à l'intérieur de la méthode de lecture. C'est à dire. si la propriété est null, puis chargez et l'affecter à la propriété, sinon retourne il.

    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 DB / d'affaires ne sera pas inutilement être exécuté sur chaque appel getter unique.

Voir aussi:

Autres conseils

Avec JSF 2.0, vous pouvez joindre un auditeur à un événement système

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

Vous pouvez également joindre la page JSF dans une balise f:view

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

      .. jsf page here...

<f:view>

Je l'ai écrit article sur la façon de mettre en cache JSF haricots getter avec Spring AOP.

Je crée 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 du printemps:

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

Je espère que ça vous aidera!

Initialement posté dans le forum PrimeFaces @ http://forum.primefaces.org/ viewtopic.php? f = 3 & t = 29546

Récemment, je suis obsédé évaluer les performances de mon application, réglage des requêtes JPA, remplacement des requêtes SQL dynamiques avec des requêtes nommées, et ce matin, je reconnais qu'une méthode de lecture a été plus d'un HOT SPOT en Java visuelle VM que le reste de mon code (ou la majorité de mon code).

méthode getter:

PageNavigationController.getGmapsAutoComplete()

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

Ci-dessous, vous verrez que PageNavigationController.getGmapsAutoComplete () est un HOT SPOT (question de performance) en Java VM visuelle. Si vous regardez plus bas, sur la capture d'écran, vous verrez que getLazyModel (), PrimeFaces paresseux getter datatable, est un point chaud aussi, que lorsque enduser fait beaucoup de type « datatable paresseux » des choses / opérations / tâches dans l'application. :)

Java VM visuelle: montrant HOT SPOT

Code Voir (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: puisque c'est une méthode « getter », déplacez le code et attribuer une valeur à gmapsAutoComplete avant la méthode 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;
    }
}

Les résultats des tests: PageNavigationController.getGmapsAutoComplete () n'est plus HOT SPOT en Java VM visuelle (ne fait même pas plus)

Partage ce sujet, car la plupart des utilisateurs d'experts ont conseillé aux développeurs d'ajouter JSF junior PAS code dans les méthodes « getter ». :)

Si vous utilisez CDI, vous pouvez utiliser des méthodes producteurs. Il sera appelé à plusieurs reprises, mais le résultat du premier appel sont mises en cache dans le champ de la fève et est efficace pour getters qui sont l'informatique ou des objets lourds d'initialisation! Voir , pour plus d'informations .

Vous pouvez probablement utiliser AOP pour créer une sorte d'aspect mis en mémoire cache les résultats de nos apporteurs pour une durée configurable. Cela vous évitera d'avoir besoin de copier-coller le code boilerplate dans des dizaines de accesseurs.

  

Si la valeur de someProperty est   cher pour calculer, cela peut   potentiellement un problème.

est ce que nous appelons une optimisation prématurée. Dans les rares cas où un profileur vous dit que le calcul d'une propriété est extraordinairement coûteuse que l'appel trois fois plutôt que a une fois un impact significatif de la performance, vous ajoutez la mise en cache que vous décrivez. Mais à moins que vous faites quelque chose de vraiment stupide comme l'affacturage ou nombres premiers accès à un databse dans un getter, votre code le plus probable est une douzaine pire des inefficacités dans des endroits que vous avez jamais pensé.

Je voudrais également des conseils en utilisant ce cadre comme Primefaces au lieu de stock JSF, ils répondre à ces questions avant l'équipe JSF e. g primefaces vous pouvez définir soumettre partielle. Sinon BalusC a bien expliqué.

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

Le contrôle de la sécurité pourrait être compliqué par exemple. requête LDAP, etc. Vous devez donc éviter que, avec

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

et vous devez vous assurer dans un bean session cette demande par.

Ich pense JSF doit mettre en œuvre ici quelques extensions pour éviter plusieurs appels (annotation @Phase(RENDER_RESPONSE) par exemple cette méthode calle une seule fois après la phase RENDER_RESPONSE ...)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top