Il concetto di Inversion of Control (IoC) ormai è noto da anni: si demanda a terzi (un Container) la responsabilità, il controllo, di istanziare i nostri oggetti e le loro interdipendenze. Se ne guadagna in termini di riusabilità e testing del codice. Come dice Martin Fowler:
Inversion of Control is too generic a term, and thus people find it confusing. As a result with a lot of discussion with various IoC advocates we settled on the name Dependency Injection
Il termine IoC è troppo generico, e “si sono inventati” il termine Dependancy Injection (DI), che effettivamente rende più l’idea su cosa fanno, tra le altre cose, framework come Spring, Seam o .
Nel mondo Java Enterpirse, la piattaforma Java EE 6, tra le varie novità, ha standardizzato le esperienze del settore, creando la specifica JSR-299. Cerchiamo di capire a cosa può servirci e come si integra con il resto della piattaforma enterprise.
Weld, la Reference Implementation (RI)
Come ogni specifica, esiste l’implementazione di riferimento: in questo caso tocca a Weld, nato principalmente dall’esperienza di Seam che, strizzando l’occhio a Spring e Guice, è presente sia in JBoss (6 e 7) che in Glassfish 3.1 in modo nativo. Il perché dovremmo aver bisogno della DI in una applicazione ormai è ovvio: cerchiamo invece di capire quando usare la DI, visto che già nella piattaforma enterprise sia JSF che EJB si comportano da IoC containers. In particolare, un EJB è necessario quando vogliamo interagire con i servizi dell’Application Server (AS), per esempio:
- gestione automatica delle transazioni e della sicurezza;
- gestione della concorrenza;
- timers e task asincroni o schedulati.
Il rapporto con JSF invece è un po’ particolare. Gli scopes dei bean CDI infatti hanno quasi tutti lo stesso nome e lo stesso comportamento di quelli JSF, ma differiscono per il package!! Non so se è voluto o meno, ma sta di fatto che un bean CDI può praticamente sostituire un Backing Bean JSF: non a caso può essere referenziato anche tramite Expression Language (EL) se annotato con @Named!
Con CDI, torna nuovamente il concetto di bean, che questa volta sembra avere una denotazione più precisa, o per meglio dire più ampia! Per JSF infatti un bean è un POJO con un ciclo di vita regolato dal framework, per EJB hanno un altro significato e un ciclo di vita completamente differente… e Spring e Seam ci hanno aggiunto del suo! In Java EE 6 si fa affidamento alla nuova specifica chiamata Managed Beans (allora la confusione con JSF è proprio voluta…):
un managed bean è un oggetto gestito da un container con minime restrizioni di programmazione, meglio conosciuto come POJO. Supporta un insieme di servizi come la dependency injection, lifecycle callbacks e interceptors.
In parole povere, ogni classe che ha un costruttore senza argomenti (o uno con l’annotazione @Inject) è un bean. Ogni tipo, che sia attributo di una classe o argomento di un metodo, annotato con @Inject è definito Injection Point, la cui istanza verrà iniettata dal container. Oltre a risolvere le dipendenze, il container gestisce le callback del tipo @PostConstruct e @PreDestroy e supporta nativamente l’AOP tramite interceptors, proprio come accade per gli EJB.
Client Proxy e Scopes
Da bravo IoC container, CDI ci restituisce dei proxy sui bean che abbiamo creato, e non diretti riferimenti ad essi, tranne alcune eccezioni. Questa distinzione si basa sugli scopes dei bean, raggruppabili in due categorie:
- Built-in Scopes:
-
- @RequestScoped
- @SessionScoped
- @ApplicationScoped
- @ConversationScoped
Ogni bean con questo tipo di scope non verrà mai referenziato direttamente, ma solo tramite un proxy.
- Pseudo Scopes:
-
- @Dependent: il ciclo di vita di un bean in questo scope dipenderà da quello del bean nel quale verrà iniettato. E’ lo scope di default di ogni bean, se non specificato.
- @Singleton: il bean sarà istanziato una sola volta.
Ogni bean client avrà una referenza diretta con i bean in questi scopes. Se per i @Dependent questo non può che essere un vantaggio, per lo scope @Singleton invece può essere un problema se iniettato in bean con scope @ConversationScoped o @SessionScoped, che sono soggetti a serializzazione. Meglio allora avere un riferimento transient al singleton, per non rischiare di ritrovarsi in deserializzazione con due istanze del singleton stesso!!
E’ bene tener presente queste eccezioni quando si sviluppa, altrimenti potremmo trovarci sorprese inattese.
Prendiamo per esempio tre bean: uno con scope @Dependent, uno @ApplicationScoped e un’altro @SessionScoped tali che:
Se per esempio il @SessionScoped venisse serializzato, che accadrebbe alle sue dipendenze? Di solito, quando un oggetto viene serializzato, lo stesso avviene per ogni oggetto che referenzia. In questo caso però avrebbe senso salvare lo stato del bean @Dependent, ma non quello di @ApplicationScoped, perché è un bean condiviso da altri @SessionScoped. Il meccanismo di client proxy permette di ottenere proprio questo comportamento: in CDI infatti, qualsiasi bean in Built-in Scope è “wrappato” da un proxy, permettendoci di recuperare sempre l’istanza dell’oggetto a cui si fa riferimento dal contesto corrente, evitando serializzazioni ricorsive degli oggetti iniettati che potrebbero avere uno stato non aggiornato. In CDI infatti è possibile iniettare, per esempio, un bean in scope Session in uno a scope Application (cioè l’inverso di quello del grafico), cosa che in JSF è impossibile. Nel momento in cui l’application bean chiamerà il bean in sessione, il proxy punterà all’oggetto corretto nella sessione corrente, garantendo la thread-safety.
A causa però di alcune limitazioni di Java, non è possibile creare proxy su alcuni tipi. Quindi, a meno che un oggetto non abbia uno Pseudo Scope, non è possibile iniettare:
- classi che non hanno un costruttore non privato senza argomenti
- classi dichiarate final o che hanno un metodo final
- array
- tipi primitivi
Per aggirare il problema, è possibile adottare diverse strategie:
- dove possibile, aggiungere un costruttore senza argomenti o annotare l’argomento con @Inject;
- modificare l’Injection Point di tipo T con il tipo Instance
; - far implementare al tipo “non proxabile” una nuova interfaccia e sostituire di conseguenza il tipo dell’Injection Point con la nuova interfaccia;
- se proprio non c’è niente da fare, modificare lo scope in @Dependent.
Conclusioni
Finalmente la piattaforma enterprise implementa un degno concorrente di Spring, dandoci a disposizione un container leggero e flessibile, perfettamente integrato nel nostro Application Server Java EE 6 preferito. Questo significa che un bean CDI può essere iniettato in un Backing Bean o in un EJB, oppure un EJB in un bean e ovviamente un bean in un altro bean: è il vero middleware che mancava per amalgamare gli strati di una applicazione enterprise!