Categorie
Java JSF

Problemi con c:forEach e JSF lifecycle

Mi sono recentemente ritrovata a risolvere un problema verificatosi in una situazione che può sembrare particolare, ma penso sia più frequente di quello che può sembrare: si tratta dell’uso del tag JSTS c:forEach in combinazione con tag JSF (in Facelets) che rappresentavano componenti aggiornate tramite PreRenderViewEvent listener.

Ho imparato a mie spese che non è saggio utilizzare insieme tag handlers JSTL e componenti; la differenza tra i due tipi di tag non è sempre lampante ma risulta evidente riflettendo sulla definizione: i tag che rappresentano componenti sono logicamente collegati ad un oggetto del managed bean, mentre i tag handler non rappresentano componenti e non diventano parte del component tree quando la view viene costruita, ma aiutano semplicemente nella costruzione stessa.

Alcuni esempi di tag handlers:

c:forEach
c:choose
c:set
c:if
f:facet
f:actionListener
f:valueChangeListener
ui:include
ui:decorate
ui:composition

Alcuni esempi di component tag:

ui:repeat
ui:fragment
ui:component
f:view
f:verbatim
f:selectItems
h:inputText
h:datatable

I problemi causati dall’uso contemporaneo di questi due tipi di tag sono principalmente dovuti al fatto che essi sono gestiti in modo diverso dal punto di vista del ciclo di vita JSF: i tag handler sono build time tag, cioè sono usati nella fase di costruzione dell’albero, mentre i component tag sono usati nella successiva fase di rendering, fase nella quale i componenti sono aggiornati a partire dalle proprietà dei managed bean.

Questo fatto fa sì che molto spesso usando insieme questi due tipi di elementi si abbiano dei risultati inaspettati, in particolare quando si ha a che fare con oggetti aggiornati dinamicamente. Situazioni di questo tipo in un progetto di medie dimensioni possono portare ad errori non facilmente individuabili.

E qui veniamo al mio caso in particolare, introducendo un ulteriore fattore che è il PreRenderViewEvent handler. Se si deve utilizzare un Managed Bean con scope di sessione capita spesso di affidarsi ad un metodo per aggiornare gli oggetti in base a parametri passati alla pagina. Tipicamente utilizzeremo un tag f:event:

<f:metadata>
    <f:event type="preRenderView" listener="#{bean.cambiaNumero}"/>
</f:metadata>

Ci potrebbe venire in mente di usare nella pagina un tag handler che utilizza un oggetto aggiornato tramite questo metodo. Bene, se ci viene in mente, non facciamolo 🙂

Vediamo un esempio. Nella pagina inserisco un ciclo per rappresentare i numeri da 0 a N, dove N è un numero pseudorandom che cambia ogni volta che ricarichiamo la pagina.

Allora nell’xhtml avrò:

  <h:outputText value="#{bean.numero}"/>
        <br/>
  <c:forEach begin="0" end="#{bean.numero}" var="n">
       <h:outputText value="#{n}"/>
  </c:forEach>

E nel bean di sessione avrò il metodo che cambia il valore della variabile numero ogni volta che viene ricaricata la pagina:

public void cambiaNumero(){
        Random r=new Random();
        numero=r.nextInt(10);
    }

Eseguendo l’esempio e ricaricando la pagina un po’ di volte vediamo dei risultati un po’ strani:

La prima riga corrisponde a <h:outputText value=”#{bean.numero}”/> e ci fa vedere quindi l’effettivo valore della variabile numero, la seconda riga invece è l’output del c:forEach. E’ evidente che c’è qualcosa che non va, e osservando più attentamente la sequenza ci accorgiamo che il valore utilizzato nel ciclo (come estremo finale) è quello che aveva la variabile nello stato precedente!

Questo succede perché il metodo invocato tramite f:event è eseguito dopo che l’albero dei componenti è stato costruito/recuperato ma prima del rendering della view; allora il componente h:outputText (che è valutato nella fase di rendering) viene correttamente aggiornato mentre il tag handler c:forEach (che entra in gioco nella fase di building) continua ad operare con il vecchio stato della variabile.

La cosa sembra evidente in questo semplice esempio ma vi assicuro che in un contesto più complesso ci ho messo un po’ per capire l’arcano! 😛

Quale può essere la soluzione?

1) Utilizziamo uno scope View per il nostro bean e aggiorniamo in valore nel metodo annotato con PostConstruct.

2) Sostituiamo il tag handler c:forEach con il “corrispondente” ui:repeat.

Le virgolette sono dovute al fatto che i due tag ovviamente non sono esattamente equivalenti; nel mio caso per esempio io volevo davvero semplicemente stampare una lista di numeri, dove l’estremo superiore cambiava in relazione ad una determinata logica. Nel caso dell’uso dell’ui:repeat non posso implementare un semplice ciclo for ma devo invece associare il tag ad un oggetto, cioè ad una lista: allora sono costretta a realizzare una dummy list di interi su cui iterare.

Nella pagina:

<ui:repeat value="#{bean.dummyList}" var="n" >
    <h:outputText value="#{n}"/>
</ui:repeat>

E nel metodo di aggiornamento nel bean:

   public void cambiaNumero(){
        Random r=new Random();
        numero=r.nextInt(10);
        dummyList.clear();

        for (int i=0; i<=numero; i++){
            dummyList.add(i);
        }
    }

In conclusione bisogna stare molto attenti ad usare i tag JSTL con JSF e Facelets, soprattuto quando si ha a che fare con oggetti aggiornati dinamicamente. E’ sempre tenere bene a mente le fasi del ciclo di vita JSF e le differenze tra i tipi di tag.

Riferimenti:

http://www.ibm.com/developerworks/library/j-jsf2/
http://balusc.blogspot.com/2011/09/communication-in-jsf-20.html
http://www.ninthavenue.com.au/blog/c:foreach-vs-ui:repeat-in-facelets

Di Evelina Agostini

Evelina Agostini si è laureata con lode in Ingegneria Informatica presso l'Università degli Studi di Firenze e può vantare un'esperienza decennale nello sviluppo di soluzioni web. Dotata di competenze trasversali a tutto lo stack, conosce in maniera approfondita la piattaforma Java EE ed in generale le tecnologie server side sia Java che PHP, ed è in grado di progettare e realizzare database scalabili utilizzando sia MySQL che Oracle. Evelina possiede inoltre competenze su Javascript e jQuery e skill internazionalmente riconosciuti per quanto riguarda il framework AngularJS ed in generale le soluzioni Javascript MVC. Evelina, grazie alle sue doti creative, si occupa anche del design di alcune delle interfacce dei sistemi sviluppati da ELbuild.

Una risposta su “Problemi con c:forEach e JSF lifecycle”

Articolo molto interessante! Anche io c’ho sbattuto la testa ed è stato un incubo trovare il motivo. Nel mio caso il “sintomo” era diverso, un bean con scope View che veniva ricreato anche ad ogni chiamata ajax (c’è voluto un breakpoint nel costruttore per capirlo!).
Da quel momento evito accuratamente tutti i tag c: !

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *