Tra le novità della piattaforma Java EE 6, sicuramente spicca CDI. Spring e Seam hanno fatto scuola e il meglio entra nella specifica Java Enperprise. Uno dei primi concetti con cui dobbiamo fare i conti è il lifecycle dei bean CDI, governato dagli scopes. Già da subito si nota che molti di questi scope si sovrappongono a quelli della specifica JSF 2, e il fatto che i bean CDI siano referenziabili anche da pagine web tramite Expression Language (EL), fa pensare che CDI stia invadendo il campo di JSF. Cerchiamo di capire su cosa.
Questione di scope
Quando annotiamo un POJO per farlo diventare un ManagedBean, il nostro vecchio caro autocomplete di Eclipse ci suggerisce due package da cui importare l’annotazione:
- javax.faces.bean
- javax.enterprise.context
Lo stesso vale anche per gli scope request e session per esempio. Ma che succede? Accade che CDI si sta facendo spazio annoverando diverse annotazioni omonime a quelle di JSF:
- @ManagedBean
- @RequestScoped
- @SessionScoped
- @ApplicationScoped
che per chi conosce JSF non hanno bisogno di presentazioni. Il comportamento è lo stesso, ma quelle di JSF appartengono al package javax.faces.bean, mentre quelle di CDI a javax.enterprise.context. Aggiungendo poi ad un bean CDI l’annotazione @Named, possiamo tranquillamente sostituire un Backing Bean JSF perché il bean sarà referenziabile tramite EL. Tutto qui? Non proprio…
Gemelli diversi
Tra le annotazioni citate manca all’appello @ViewScoped, che esiste solo in JSF: per CDI infatti non avrebbe senso, perché è uno scope legato esclusivamente alle view delle pagine JSF. In CDI invece, questo concetto è sostituito da quello di conversazione, definito dall’annotazione @ConversationScoped: si tratta di uno scope che può avere un ciclo di vita intermedio tra request e session. A prima vista sembra praticamente identico al @ViewScoped, ma in realtà ha notevoli differenze:
- lo scope di conversazione mantiene lo stato di un tab del browser indipendente da un’altro, cosa che invece normalmente non accade perché i browser tendono a condividere i cookie tra tab. Questo è possibile perché lo scope non è gestito a livello di web container (quindi tramite cookie e sessione), ma ad un livello diverso, cioè quello del container CDI.
- la conversazione è demarcata esplicitamente nell’applicazione: questo significa che se si vuole che sia attiva tra più richieste (long-running conversation) deve essere programmaticamente iniziata, propagata e conclusa. Il @ViewScoped invece segue automaticamente il ciclo di vita della view a cui è legato: una volta che viene caricata una nuova pagina, lo scope viewtermina.L’inizio e la fine di una conversazione vengono definiti come nell’esempio seguente:
@ConversationScoped @Named public class MyBean implements Serializable { @Inject private Conversation conversation; @PostConstruct void init() { conversation.begin(); } ... public void submit() { conversation.end(); } }
- Ogni conversazione ha un proprio codice identificativo (id): la propagazione della conversazione tra più richieste necessita di questo codice. Affinché una richiesta sia partecipe di una certa conversazione, deve essere passato il suo id come valore del parametro cid, come da esempio:
- Il maggior controllo che si ha con il @ConversationScoped permette di mantenere viva la conversazione anche tra pagine diverse, persino se implementiamo il Post/Redirect/Get Pattern, cosa che con JSF non è possibile.
Manca ancora qualcuno…
Tra gli scope JSF manca ancora @NoneScoped, che può essere paragonato allo scope di default di CDI, ovvero @Dependent: in entrambe i casi significa che lo scope del bean dipende da quello nel quale viene iniettato!
Conclusioni
Alla fine chi vince? JSF o CDI? Difficile dirlo. Come in tutti i casi, la verità sta nel mezzo. Personalmente, faccio largo uso di @ViewScoped e troverei difficile farne a meno. Il controllo programmatico della conversazione è molto potente, ma dover trovare un modo per chiuderla esplicitamente forse non si sposa con tutti i modelli di interazione utente. A dire il vero l’oggetto Conversation
avrebbe un metodo per impostare un timeout, oltre il quale la conversazione e tutto quello ad essa legato viene distrutto, ma si tratta di un “hint”, un suggerimento per il container, ovvero è libero di ignorarlo!
Essendo però CDI cross-layer, ovvero raggiungibile sia da JSF che EJB per intendersi, potrebbe essere molto utile invece usare il @SessionScoped di CDI per salvare informazioni sullo stato della sessione corrente (per esempio quelle dell’utente loggato), in modo da essere facilmente raggiungibili da tutti gli strati dell’applicazione.