Perché JSF chiama i getter più volte

Diciamo che ho specificato un componente outputText come questo:

 

Se someProperty un messaggio di registro quando viene chiamato il getter per someProperty e carica la pagina, è banale notare che il getter viene chiamato più di una volta per richiesta (due o tre volte è ciò che è accaduto nel mio caso):

 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 

Se il valore di someProperty è costoso da calcolare, questo può potenzialmente essere un problema.

Ho cercato su Google un po ‘e ho capito che si tratta di un problema noto. Una soluzione alternativa era includere un controllo e vedere se era già stato calcolato:

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

Il problema principale con questo è che si ottiene un sacco di codice boilerplate, per non parlare delle variabili private che potrebbero non essere necessarie.

Quali sono le alternative a questo approccio? C’è un modo per ottenere questo senza tanto codice inutile? C’è un modo per impedire a JSF di comportarsi in questo modo?

Grazie per il tuo contributo!

Questo è causato dalla natura delle espressioni differite #{} (si noti che le espressioni standard “legacy” ${} comportano esattamente allo stesso modo quando si usa Facelets invece di JSP). L’espressione posticipata non viene immediatamente valutata, ma creata come object ValueExpression e il metodo getter dietro l’espressione viene eseguito ogni volta che il codice chiama ValueExpression#getValue() .

Normalmente questo viene invocato una o due volte per ogni ciclo di richiesta-risposta JSF, a seconda che il componente sia un componente di input o di output ( apprendi qui ). Tuttavia, questo conteggio può aumentare (molto) di più quando viene utilizzato nell’iterazione di componenti JSF (come e ), o qua e là in un’espressione booleana come l’attributo rendered . JSF (in particolare, EL) non memorizzerà nel cache il risultato valutato dell’espressione EL poiché potrebbe restituire valori diversi su ciascuna chiamata (ad esempio, quando è dipendente dalla riga datatable iterata attualmente).

Valutare un’espressione EL e invocare un metodo getter è un’operazione molto economica, quindi in generale non dovresti preoccuparti di questo. Tuttavia, la storia cambia quando si eseguono costosi DB / business logic nel metodo getter per qualche motivo. Questo sarebbe stato rieseguito ogni volta!

I metodi Getter nei bean di backing JSF devono essere progettati in modo tale da restituire esclusivamente la proprietà già preparata e nient’altro, esattamente come previsto dalla specifica Javabeans . Non dovrebbero assolutamente fare alcun costoso DB / business logic. Per questo dovrebbero essere usati i metodi del listener @PostConstruct e / o (action) del bean. Esse vengono eseguite una sola volta ad un certo punto del ciclo di vita JSF basato sulla richiesta e questo è esattamente quello che vuoi.

Ecco un riepilogo di tutti i diversi modi giusti per preimpostare / caricare una proprietà.

 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 (eg ). someProperty = loadSomeProperty(); } public void preRender(ComponentSystemEvent event) { // Or in some SystemEvent method (eg ). someProperty = loadSomeProperty(); } public void change(ValueChangeEvent event) { // Or in some FacesEvent method (eg ). someProperty = loadSomeProperty(); } public void ajaxListener(AjaxBehaviorEvent event) { // Or in some BehaviorEvent method (eg ). someProperty = loadSomeProperty(); } public void actionListener(ActionEvent event) { // Or in some ActionEvent method (eg ). someProperty = loadSomeProperty(); } public String submit() { // Or in POST action method (eg ). someProperty = loadSomeProperty(); return "outcome"; } public SomeObject getSomeProperty() { // Just keep getter untouched. It isn't intented to do business logic! return someProperty; } } 

Si noti che non si deve utilizzare il costruttore del bean o il blocco di inizializzazione per il lavoro perché potrebbe essere invocato più volte se si utilizza un framework di gestione dei bean che utilizza proxy, come CDI.

Se per te non ci sono davvero altri modi, a causa di alcuni requisiti di progettazione restrittivi, allora dovresti introdurre il caricamento pigro all’interno del metodo getter. Vale a dire se la proprietà è null , quindi caricarlo e assegnarlo alla proprietà, altrimenti restituirlo.

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

In questo modo la costosa logica DB / business non verrà eseguita inutilmente su ogni singola chiamata getter.

Guarda anche:

  • Perché il getter viene chiamato così tante volte dall’attributo reso?
  • Richiama azione bean gestita JSF al caricamento della pagina
  • Come e quando dovrei caricare il modello dal database per h: dataTable
  • Come popolare le opzioni di h: selectOneMenu dal database?
  • Visualizza l’immagine dynamic dal database con p: graphicImage e StreamedContent
  • Definizione e riutilizzo di una variabile EL nella pagina JSF

Con JSF 2.0 è ansible colbind un listener a un evento di sistema

    

In alternativa puoi racchiudere la pagina JSF in un tag f:view

   .. jsf page here...  

Ho scritto un articolo su come memorizzare in cache i bean JSF con Spring AOP.

Creo un MethodInterceptor semplice che intercetta tutti i metodi annotati con un’annotazione speciale:

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

Questo intercettore viene utilizzato in un file di configurazione di spring:

               

Spero che ti aiuti!

Originariamente pubblicato nel forum PrimeFaces @ http://forum.primefaces.org/viewtopic.php?f=3&t=29546

Recentemente, sono stato ossessionato dalla valutazione delle prestazioni della mia app, dall’ottimizzazione delle query JPA, dalla sostituzione delle query SQL dinamiche con le query denominate e, stamattina, ho riconosciuto che un metodo getter era più di un HOT SPOT in Java Visual VM rispetto al resto di il mio codice (o la maggior parte del mio codice).

Metodo Getter:

 PageNavigationController.getGmapsAutoComplete() 

Riferimento a ui: include in index.xhtml

Di seguito, vedrai che PageNavigationController.getGmapsAutoComplete () è un HOT SPOT (problema di prestazioni) in Java Visual VM. Se guardi più in basso, nella cattura dello schermo, vedrai che getLazyModel (), il metodo getter datatable lazy di PrimeFaces, è anche un punto caldo, solo quando enduser sta facendo un sacco di tipo ‘lazy datatable’ di roba / operazioni / attività nell’app. 🙂

Java Visual VM: mostra HOT SPOT

Vedere il codice (originale) sotto.

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

Riferito da quanto segue in index.xhtml:

    

Soluzione: poiché questo è un metodo ‘getter’, sposta il codice e assegna valore a gmapsAutoComplete prima di chiamare il metodo; vedi il codice qui sotto.

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

Risultati dei test: PageNavigationController.getGmapsAutoComplete () non è più un HOT SPOT in Java Visual VM (non viene più visualizzato)

Condividendo questo argomento, dal momento che molti utenti esperti hanno consigliato agli sviluppatori JSF junior di NON aggiungere codice nei metodi ‘getter’. 🙂

Probabilmente potresti utilizzare AOP per creare una sorta di Aspect che memorizza nella cache i risultati dei nostri getter per un periodo di tempo configurabile. Questo ti impedirebbe di dover copiare e incollare il codice boilerplate in dozzine di accessor.

Se si utilizza CDI, è ansible utilizzare i metodi Producers. Verrà chiamato più volte, ma il risultato della prima chiamata viene memorizzato nella cache del bean ed è efficiente per i getter che stanno calcolando o inizializzando oggetti pesanti! Vedi qui , per maggiori informazioni.

È ancora un grosso problema in JSF. Per esempio se hai un metodo isPermittedToBlaBla per i controlli di sicurezza e nella tua vista hai rendered="#{bean.isPermittedToBlaBla} allora il metodo sarà chiamato più volte.

Il controllo di sicurezza potrebbe essere complicato ad es. Query LDAP ecc. Quindi devi evitarlo con

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

e devi garantire all’interno di un bean di sessione questo per richiesta.

Ich pensa che JSF debba implementare qui alcune estensioni per evitare chiamate multiple (es. Annotation @Phase(RENDER_RESPONSE) calle questo metodo solo una volta dopo la fase RENDER_RESPONSE …)

Se il valore di someProperty è costoso da calcolare, questo può potenzialmente essere un problema.

Questo è ciò che chiamiamo un’ottimizzazione prematura. Nei rari casi in cui un profiler ti dice che il calcolo di una proprietà è così straordinariamente costoso che chiamarlo tre volte anziché una volta ha un impatto significativo sulle prestazioni, aggiungi la cache come descrivi. Ma a meno che tu non faccia qualcosa di davvero stupido come il fattorizzare numeri primi o accedere a un databse in un getter, il tuo codice ha probabilmente una dozzina di inefficienze peggiori in luoghi che non hai mai pensato.