Il ViewScoped in JSF 2 è molto usato e molto importante: ci permette di salvare lo stato di una vista per il solo arco di tempo in cui questa è attiva. Recentemente abbiamo introdotti dalla specifica CDI. Sarebbe bello quindi poter usare la potenza dei bean CDI con la semplicità della gestione del ViewScoped. Come? Esistono diverse vie.
ViewScoped con Seam: resistenza e resa
Vista la grande somiglianza tra gli scope JSF e CDI, in tanti hanno già pensato a portare lo scope mancante in CDI. Per esempio Seam 3: uno dei moduli di Seam è proprio Seam Faces, ovvero un modulo di integrazione e utilità tra JSF e CDI che, tra le altre cose, abilita il ViewScoped per CDI. Mettiamolo alla prova! Prendiamo quindi il nostro progetto Maven e aggiungiamo le dipendenze: sembra facile ma… già all’avvio dell’applicazione, con JBoss AS 7.1.1 si scopre che manca qualcosa: NoClassDefFoundException
come il giorno del giudizio! Comincia quindi la spirale di dipendenze mancanti che mi portano a tirare in ballo altre dipendenze:
- seam-persistence
- prettyfaces-jsf2
- seam-security
- joda-time
- org.apache.servicemix.bundles.drools
e poi mi sono fermato: probabilmente sto sbagliando qualcosa! Un po’ troppe dipendenze per un semplice ViewScoped… Cercando in rete, esiste ancora un bug aperto sull’implementazione offerta da Seam: sostanzialmente si contesta il fatto che il bean creato dal container non sia in “normal scope” ma uno “pseudo scope“, ovvero che l’accesso al bean è diretto, non tramite proxy. Proviamo allora un’altra strada.
ViewScoped fai-da-te
Il sito stesso di Seam suggerisce una implementazione semplificata dello scope, facendo riferimento al blog di Steven Verborgh. La soluzione proposta è molto interessante, e soprattutto funzionante! In sostanza ecco cosa bisogna fare per avere il nostro scope:
- Implementare l’interfaccia
javax.enterprise.context.spi.Context
, che fa parte della SPI di CDI (che non è uno scioglilingua…). Verborgh ci dice che in sostanza uno scope non è altro che una implementazione di un Context, a cui andiamo a chiedere i nostri bean. In questo caso, il Context si appoggerà alla view map associata alla vista JSF corrente, in modo da creare o recuperare da essa il bean richiesto dalla pagina. - Creare una annotazione CDI per identificare lo scope come normal scope. Non è possibile usare ViewScoped di JSF, per cui è necessario crearne una nuova. Verborgh suggerisce
be.verborgh.faces.ViewScoped
: per evitare confusione, preferirei chiamarlaPageScoped
, in onore di Seam 2 😉 . - Estendere il container CDI con il nuovo scope implementando l’interfaccia
javax.enterprise.inject.spi.Extension
e registrandosi l’eventoAfterBeanDiscovery
del ciclo di vita del contesto CDI. - Attivare l’estensione creando un file di testo contenente il full qualified name dell’estensione all’interno di un file di testo chiamato:
“javax.enterprise.inject.spi.Extension“.
Il quale è poi da posizionare sotto la cartella META-INF/services del proprio progetto.
Per tutti i dettagli, vi rimando al post di Steven Verborgh. Come nel caso di Seam Faces, il bean che si ottiene è però in pseudo scope, nonostante che l’annotazione creata sia definita come normal scope. Basta ricordarsi questa cosa se scegliamo di procedere con questa soluzione.
ViewScoed out-of-the-box
Tutto questo è necessario se stiamo lavorando con la Reference Implementatin di CDI, cioè Weld, ovvero se abbiamo a che fare con JBoss AS 7 o Glassfish 3.1. Altre implementazioni, come OpenWebBeans (incluso in WebSphere 8) o estensioni, come CODI, hanno già pensato a JSF, andando oltre la specifica.
Se non vogliamo (o non possiamo) cambiare implementazione di CDI o portarci dietro dipendenze di terze parti, possiamo prendere però spunto da OpenWebBeans per esempio, e con un paio di classi ci portiamo a casa la loro interpretazione del ViewScoped!
Il meccanismo è lo stesso illustrato in precedenza: abbiamo bisogno di una Extension
, di un Context
e di un attivatore dell’estensione. Diamo quindi un’occhiata al codice della classe Jsf2ScopesExtension. Apparte il riferimento a classi di utilità interne che possiamo trascurare, vediamo che l’annotazione che identifica il nostro scope è proprio quella di JSF (javax.faces.bean.ViewScoped
).
La classe che invece definisce il contesto è ViewScopedContext. A differenza di quella del blog, è interessante notare che viene implementata anche l’interfaccia SystemEventListener
che permette di gestire l’evento PreDestroyViewMapEvent
: in questo modo siamo sicuri che i nostri oggetti verranno rimossi dal contesto al cambiamento di view. Ricordiamoci di attivare l’estensione come visto precedentemente e abbandoniamo i backing bean JSF!!
Conclusioni
Con questa soluzione crolla l’unico motivo per cui dovremmo continuare ad usare i managed bean JSF al posto di quelli CDI. Con l’abilitazione dello scope View, siamo liberi di decorare o intercettare a piacimento i controller delle nostre pagine JSF. Occhio solo al fatto che anche questa implementazione produce accessi diretti ai bean (invece che tramite proxy), nonostante lo scope sia inizializzato come “normal”.