CDI ViewScoped: abbandoniamo i backing bean JSF!

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 chiamarla PageScoped, in onore di Seam 2 😉 .
  • Estendere il container CDI con il nuovo scope implementando l’interfaccia javax.enterprise.inject.spi.Extension
    e registrandosi l’evento AfterBeanDiscovery 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”.

Andrea Como

Sono un software engineer focalizzato nella progettazione e sviluppo di applicazioni web in Java. Presso OmniaGroup ricopro il ruolo di Tech Leader sulle tecnologie legate alla piattaforma Java EE 5 (come WebSphere 7.0, EJB3, JPA 1 (EclipseLink), JSF 1.2 (Mojarra) e RichFaces 3) e Java EE 6 con JBoss AS 7, in particolare di CDI, JAX-RS, nonché di EJB 3.1, JPA2, JSF2 e RichFaces 4. Al momento mi occupo di ECM, in particolar modo sulla customizzazione di Alfresco 4 e sulla sua installazione con tecnologie da devops come Vagrant e Chef. In passato ho lavorato con la piattaforma alternativa alla enterprise per lo sviluppo web: Java SE 6, Tomcat 6, Hibernate 3 e Spring 2.5. Nei ritagli di tempo sviluppo siti web in PHP e ASP. Per maggiori informazioni consulta il mio . - -

  • lucaster

    Hai desistito dall’usare Seam Faces perchè non riuscivi a sistemare le dipendenze?

    • Mi sono fermato perché erano troppi mb di jar da tirarsi dietro per un semplice view scoped… mi trovo meglio con CODI anche se la documentazione lascia un po’ a desiderare