La volta scorsa avevamo interrotto la trattazione di CDI parlando dei pattern. In questo quarto articolo termineremo di analizzare CDI finendo la discussione sui pattern e mostrando le funzionalità più avanzate.
L’ultima volta ci eravamo lasciati parlando dei pattern e riassumendo quelli di cui avevamo già parlato e quelli di cui parleremo in questo ultimo articolo della serie. In pratica la situazione era composta da patterns creazionali: Factory Method (@Produces), Singleton Factory Method (@Singleton); patterns strutturali: Decorator (@Decorator); patterns comportamentali: Alternatives (@Alternative), Dependency Injection (@Inject), Interceptors (@Interceptor), Observer (@Observers, @Event).
Factory Method e Singleton
In CDI è possibile definire quali metodi usare per creare degli oggetti e quali usare per distruggerli. Questo meccanismo è molto comodo per le risorse: può risultare comodo effettuare delle operazioni anche alla chiusura delle risorse (per esempio può essere comodo chiamare il metodo di chiusura di una connessione prima di distruggerla).
Vediamo un esempio. Definiamo un servizio factory singleton che crea degli oggetti Message qualificati con @Preferred, inoltre questo servizio si preoccuperà anche di distruggere gli stessi oggetti Message.
Ecco la definizione di @Preferred:
@Qualifier @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) public @interface Preferred { }
Ora definiamo un servizio Singleton che crea e distrugge oggetti Message qualificati con @Preferred:
@Singleton public class MessageService { private static int counter = 0; public MessageService() { ++counter; } public static int getCounter() { return counter; } @Produces @Preferred @Dependent public Message getMessage() { System.out.println("Message produced using @Preferred qualifier."); Message message = new Message(" @Preferred World!"); return message; } public void closeMessage(@Disposes @Preferred Message message) { System.out.println("Disposes @Preferred message previously created."); } }
Abbiamo definito lo scope degli oggetti Message come @Dependent in modo tale da vedere in azione il metodo closeMessage() per ogni oggetto creato:
@RunWith(Arquillian.class) public class ServiceTest { … @Inject MessageService service1; @Inject MessageService service2; @Inject MessageService service3; @Inject @Preferred Message message; @Test public void testService() throws Exception { System.out.println("Message : " +message.toString()); System.out.println("How many instances? " +MessageService.getCounter()); Assert.assertTrue("All services must be the same because are annotated with @Singleton",service1 == service2 && service2 == service3); } }
Come si vede la classe dichiara tre instanze diverse di MessageService, ma in realtà CDI ne creerà una sola e assegnerà a tutte e tre i campi di tipo MessageService lo stesso oggetto.
Inoltre, visto che viene richiesta una instanza di Message, CDI chiamerà il metodo di creazione di MessageService e, al termine del suo ciclo di vita, il metodo di distruzione. Ecco il log di Jboss:
12:19:19,068 INFO [org.jboss.as.server] (management-handler-thread - 14) JBAS018559: Deployed "test.war" 12:19:19,280 INFO [stdout] (pool-3-thread-4) Message produced using @Preferred qualifier. 12:19:19,285 INFO [stdout] (pool-3-thread-4) Message : Hello @Preferred World! 12:19:19,291 INFO [stdout] (pool-3-thread-4) How many instances? 1 12:19:19,297 INFO [stdout] (pool-3-thread-4) Disposes @Preferred message previously created. 12:19:19,399 INFO [org.jboss.weld.deployer] (MSC service thread 1-4) JBAS016009: Stopping weld service for deployment test.war
Alternatives
Con Alternatives si intende un meccanismo per cui CDI decide quale implementazione di un Bean fornire in base a quanto sta scritto nel file di configurazione beans.xml. Ho messo questo meccanismo tra i pattern comportamentali perchè permette di cambiare il comportamento di un nostro servizio (proprio come da pattern Algorithm del GoF [8]).
Questo meccanismo viene usato per poter utilizzare implementazioni diverse per stesse interfacce: per esempio è possibile utilizzare degli EntityManager differenti a seconda se utilizziamo una configurazione di “produzione” o una di “test”, oppure permette di implementare delle classi “mock” per utilizzarle solo in configurazione di test.
Negli esempi allegati a questo articolo (menu a destra) troverete due progetti che illustrano l’utilizzo di questo pattern.
alternative-example
In questo esempio definiamo un servizio Resources che implementa una interfaccia IResources:
public interface IResources { EntityManager createDefaultEntityManager(); Logger produceLog(InjectionPoint injectionPoint); }
@Default @Model public class Resources implements IResources { @PostConstruct public void init() { System.out.println("Default Resources loaded"); } @PersistenceContext(unitName="primary") private EntityManager entityManager; @Produces @RequestScoped public EntityManager createDefaultEntityManager() { return this.entityManager; } @Produces public Logger produceLog(InjectionPoint injectionPoint) { return Logger.getLogger(injectionPoint.getMember().getDeclaringClass() .getName()); } }
Tutto il modulo CDI utilizza la classe di risorse Resource che fornisce un EntityManager per accedere a un DB ed utilizzare l’entity bean Member. Nel progetto abbiamo anche definito un controller MemberRegistration usato per effettuare delle operazioni su Member.
Nella definizione “di produzione” del file bean.xml abbiamo definito una persistence unit “primary“, mentre nei pacchetti di test abbiamo definito una persistence unit “test-datasource” che viene utilizzata da una implementazione alternativa a Resource attivata tramite il file beans.xml del progetto di test.
Per fare questo abbiamo definito un servizio alternativo a Resources: AlternativeResources, che fa riferimento esplicito al datasource di test:
@Alternative @Model public class AlternativeResources implements IResources { @PostConstruct public void init() { System.out.println("Alternative Resources loaded"); } //Using test-datasource @PersistenceContext(unitName="test-datasource") private EntityManager entityManager; … }
Ecco la definizione di beans.xml contenuta nel pacchetto di test che attiva l’alternativa AlternativeResources:
xmlns_xsi=http://www.w3.org/2001/XMLSchema-instance xsi_schemaLocation="http://java.sun.com/xml/ns/javaee http://jboss.org/schema/cdi/beans_1_0.xsd"> org.mokabyte.test.AlternativeResources
In questa maniera possiamo cambiare database in maniera automatica a seconda che lanciamo il programma per fare dei test o lo lanciamo in produzione (cioè un deploy effettuato in modo “nominale” dentro JBoss invece che lanciato tramite Arquillan in fase di test).
alternative-qualifier-example
In questo esempio il meccanismo che ci permette di scegliere se usare l’implementazione nominale o alternativa a un servizio viene demandata all’uso di un marker, @Mock, che è una @Interface di tipo @Alternative @Stereotype:
@Retention(RUNTIME) @Target({ TYPE, METHOD, FIELD, PARAMETER }) @Stereotype @Alternative public @interface Mock { }
@Mock viene attivato sempre tramite il file beans.xml definito nei pacchetti di test: quindi ogniqualvolta CDI incontra una implementazione alternativa taggata con @Mock, la utilizzerà in luogo dei servizi nominali.
Ecco la definizione del servizio taggato da @Mock:
@Named("service") @Mock public class AnotherService implements IService { /** * @see org.mokabyte.alternatives.IService#sayHello() */ public String sayHello() { return "Another service"; } }
Il nome dato a questo servizio è service, che è lo stesso di quello dato al servizio nominale (in questa maniera vengono referenziati con lo stesso nome):
@Named("service") public class Service implements IService { /** * @see org.mokabyte.alternatives.IService#sayHello() */ public String sayHello() { return "Default Service"; } }
L’attivazione di mock viene effettuata come al solito tramite il file beans.xml:
="http://www.w3.org/2001/XMLSchema-instance" xsi_schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd"> org.mokabyte.alternatives.Mock
Decorators
Secondo il GoF [8], il pattern Decorator serve per arricchire il comportamento di un oggetto reimplementando completamente la sua interfaccia e arricchendo solamente quelle funzioni che servono. Tramite CDI è possibile decorare un componente senza dover per forza riscrivere tutta l’interfaccia del decorato. Ecco un esempio (progetto decorator). Supponiamo di dover implementare un servizio ICalc, che offre i servizi di somma, moltiplicazione e sottrazione.
public interface ICalc { int sum(int a, int b); int subtract(int a, int b); int multiply(int a, int b); }
Il servizio viene implementato nominalmente da Calc, che effettua le operazioni aritmetiche come ci aspettiamo.
public class Calc implements ICalc{ @Override public int sum(int a, int b) { return a+b; } @Override public int subtract(int a, int b) { return a-b; } @Override public int multiply(int a, int b) { return a*b; } }
Supponiamo ora di voler modificare il comportamento di Calc solamente nel caso della sottrazione: se la sottrazione dà un numero negativo, allora vogliamo che il risultato sia sempre zero. Questa modifica comportamentale possiamo farla seguendo due possibili strade:
- modificando il sorgente di Calc;
- creando una classe DecoratedCalc che implementa tutti i metodi di ICalc usando Calc come delegate.
Il primo caso offre svantaggi e vantaggi. Non sempre è possibile modificare il sorgente di una classe, specialmente se non è stata scritta da noi, e proviene da librerie di terze parti (probabilmente abbiamo solo il .class);
Può darsi che non ci sia nulla che non vada nella classe Calc: la modifica del servizio “sottrazione” non è una modifica che andrebbe propagata su tutto il progetto su cui stiamo lavorando: la modifica del servizio sottrazione (abbiamo detto che vogliamo avere zero se il risultato è negativo) va bene solo per alcuni casi ben specifici, e in particolare va bene solo su un sotto-componente su cui stiamo lavorando, non su tutto il progetto.
L’unico caso in cui abbiamo un vantaggio nel modificare il sorgente di Calc (ammesso che abbiamo a disposizione il sorgente e che possiamo modificarlo) è se ha un bug: se decidiamo che il servizio “sottrazione” non dovrebbe mai dare un numero negativo, allora stiamo dicendo che Calc ha un bug e che va risolto e reso disponibile a tutto il progetto.
Ma in tutti gli altri casi sarebbe meglio tentare di rendere le modifiche più “locali” possibili, in modo tale da invalidare il numero minore possibile di test unitari e di integrazione. La soluzione di utilizzare il pattern Decorator appare quindi la migliore.
L’implementazione di questo pattern consiste nel definire un campo delegato del servizio originario (Calc) ed utilizzarlo per implementare tutti i servizi dell’interfaccia. Anche questo approccio presenta ha qualche problema, e vediamo di seguito i principali inconvenienti:
- La classe che “decora” Calc deve implementare per forza tutti i metodi dell’interfaccia ICalc, anche se poi ne deve decorare solo uno: la sottrazione. Questo inconveniente non è da sottovalutare perch�, in alcuni casi, le interfacce dei servizi possono presentare un numero elevato di metodi: questo ci costringerà a definire delle classi “decorator” con un numero elevato di righe di codice quando invece, effettivamente, aggiungono o modificano solo pochi comportamenti al servizio originario.
- Non abbiamo alcun controllo per definire quando il decoratore va usato al posto del servizio originario e quando invece non va usato, a meno chè non instanziamo “a mano” la classe DecoratedCalc quando serve e in tutti gli altri casi lasciamo tutto così com’è.
Questi due problemi vengono risolti in modo elegante da CDI. Quando definiamo un decoratore definiamo solamente il metodo da cambiare e non tutti gli altri: in questo modo la classe decoratore risulterà molto “corta” e di facile manutenzione.
L’attivazione del decoratore si fa, come al solito, tramite il file beans.xml: i moduli che vorranno utilizzare il decoratore in luogo del servizio nominale non dovranno far altro che dichiararlo nel file beans.xml.
@Decorator public abstract class DecoratedCalc implements ICalc { @Inject @Delegate @Any private ICalc calc; public DecoratedCalc() { // empty } @Override public int subtract(int a, int b) { if ( a < b ) { return 0; } return calc.subtract(a, b); } }
La classe @Decorator di CDI è astratta perchè effettivamente implementa solo un sottoinsieme di metodi dell’interfaccia dichiarata. Ci penserà il motore CDI ad implementare per noi la parte restante dell’interfaccia del @Decorator utilizzando il campo delegato iniettato (@Inject @Delegate) all’interno di DecoratedCalc.
Ecco il file beans.xml che attiva il decoratore:
xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xsi_schemaLocation="http://java.sun.com/xml/ns/javaee http://jboss.org/schema/cdi/beans_1_0.xsd"> org.mokabyte.decorator.DecoratedCalc
Interceptors
Sia i Managed Beans che gli EJB Session e Message-Driven Beans supportano gli Interceptors. Gli interceptor vengono usati per separare l’implementazione dei cosidetti crosscutting concepts dai concetti di business.
Questo vuol dire che quando abbiamo a che fare con un concetto “trasversale”, cioè che interessa trasversalmente differenti parti del nostro codice (non si tratta quindi di concetti centrali, cioè “business”), allora sarebbe meglio implementarli in classi separate rispetto alle classi usate per implementare i concetti business e legarli assieme tramite annotazioni.
La classe che implementa un concetto trasversale viene annotata con @Interceptor, mentre la classe business che è interessata alle funzionalità implementate dall’interceptor dovrà essere annotata con l‘annotazione dell’@Interceptor corrispondente.
Riprendiamo l’esempio precedente del servizio ICalc. Supponiamo di aver implementato il servizio ICalc tramite la classe Calc. Questa volta vogliamo che ad ogni operazione di Calc veniamo avvisati mediante una stampa a schermo sull’operazione fatta e il risultato ottenuto. Il requisito di stampa a schermo è ovviamente un requisito di tipo cross-cut perchè non è afferente al problema che vogliamo risolvere (implementare una calcolatrice) e probabilmente il servizio di logging dovremo utilizzarlo anche per altri servizi.
Inoltre non vogliamo in alcun modo impastare assieme il codice per effettuare le operazioni di Calc e quelle per effettuare la stampa a schermo. Quindi definiamo prima di tutto l’interceptor binding type che definisce l’azione di stampare a schermo. Un Interceptor binding type è una annotazione Java di tipo @InterceptorBinding, la retention è RUNTIME ed è applicata a TYPE o anche METHOD, il che vuol dire che possiamo annotare con il nostro interceptor sia le classi che anche i singoli metodi. Ecco la definizione dell’interfaccia Logging:
@InterceptorBinding @Inherited @Target({ TYPE, METHOD }) @Retention(RUNTIME) @Documented public @interface Logging { }
La classe interceptor che dovrà eseguire delle operazioni viene annotata con l’annotazione @Logging per indicare quale è l’interceptor binding type corrispondente (cioè quale annotazione attiva questo interceptor).
@Interceptor @Logging public class Logger { public Logger() { // empty } @AroundInvoke public Object manage(InvocationContext ic) throws Exception { System.out.println("Method: "+ ic.getMethod().getName()); for ( Object param : ic.getParameters()) { System.out.println("param: "+ param); } Object result = ic.proceed(); System.out.println("result: "+ result); return result; } }
A questo punto abbiamo definito l’interceptor ed il corrispettivo binding type; attiviamo l’aspetto nella classe Calc nel seguente modo:
@Logging public class Calc implements ICalc{ ... }
Come si vede è stato sufficiente annotare la classe Calc con @Logging per poter eseguire l’aspetto around su tutti i metodi della classe. Alternativamente avremmo potuto mettere l’annotazione @Logging solo su alcuni metodi e non sulla classe, in questo modo avremmo attivato l’aspetto solo su alcune operazioni specifiche della calcolatrice.
Anche in questo caso, per attivare questo comportamento, è necessario registrare l’interceptor dentro il file beans.xml altrimenti CDI ignorerà l’annotazione posta sulla classe Calc:
xmlns_xsi=http://www.w3.org/2001/XMLSchema-instance xsi_schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd"> org.mokabyte.interceptor.Logger
Observer (Event Model)
Il modello a eventi [8] è il fondamento del paradigma di programmazione delle moderne interfacce grafiche, per esempio le Swing sono un framework completamente basato su questo pattern. Nel framework CDI, ogni bean può emettere e consumare eventi: in questa maniera è possibile sincronizzare lo stato di differenti bean in maniera completamente disaccoppiata. Un evento comprende:
- l’oggetto che rappresenta l’evento (una semplice classe java che svolge la funzione di payload);
- un insieme (anche vuoto) di qualificatori che qualificano l’evento lanciato svolgendo la funzione di selettori permettendo agli osservatori di decidere quali qualificatori osservare e quali no;
- un insieme di metodi di ascolto dell’evento che specificano: il tipo di evento da ascoltare (event type); un insieme di qualificatori da selezione, (se l’insieme di qualificatori è vuoto, allora vuol dire che il metodo ascolta ogni evento del tipo specificato senza fare alcuna selezione).
Vediamo un esempio. Supponiamo di voler lanciare un evento che indichi l’avvenuto login di un utente. Definiamo quindi l’event type LoggedInEvent che ci serve per poter trasmettere agli ascoltatori l’informazione voluta (il payload che presenta è l’oggetto User, che racchiude le informazioni sull’utente):
public class LoggedInEvent implements Serializable{ private User user; public LoggedInEvent() { super(); } public LoggedInEvent(User user) { this.user = user; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } }
Definiamo ora un qualificatore per questo tipo di evento. Il qualificatore che ci serve deve indicare se l’utente che ha fatto il login è un amministratore oppure un utente non privilegiato. Definiamo quindi il tipo di qualificatore con una @interface che ha come proprietà una stringa che indica il tipo di utente:
@Qualifier @Target(PARAMETER) @Retention(RUNTIME) public @interface Role { String value(); }
Il qualificatore Role ha la proprietà value che può assumere i valori ADMIN o USER a seconda del caso in cui il login è stato effettuato da un amministratore o meno. Definiamo ora il qualificatore vero e proprio (basato su Role) che verrà usato per poter selezionare il tipo di evento da ascoltare:
public abstract class RoleQualifier extends AnnotationLiteral implements Role { //Empty. }
Mentre il RoleQualifier viene usato dal produttore dell’evento per selezionare l’evento da lanciare, il qualificatore @Role verrà usato dai clienti che ascoltano l’evento.
Quindi un produttore di eventi deve:
- selezionare il qualificatore voluto mediante RoleQualifier;
- creare un nuovo evento con il payload da trasmettere ai vari clienti;
- lanciare l’evento usando la classe template javax.enterprise.event.EventEvent.
Ecco un esempio di un servizio che, dopo aver effettuato il login, lancia l’evento LoggedInEvent in base al tipo di utente interessato:
@Singleton @Named public class UserAdmin { @Inject @Any private Event event; public void loginUser(String userName, String password) { boolean isAdmin = "moka".equals(userName); final User user = new User(userName, password , isAdmin); event.select(new RoleQualifier() { @Override public String value() { return user.isAdmin() ? "ADMIN" : "USER"; } }).fire(new LoggedInEvent(user)); } }
Come si vede il servizio UserAdmin ha come campo privato un oggetto event iniettato da CDI. Nel metodo di login, dopo aver creato l’oggetto User, seleziona il tipo qualificatore da usare e lancia l’evento creando l’oggetto LoggedInEvent. In questa maniera riusciamo a produrre due informazioni importanti: il tipo di login effettuato e il payload dell’evento (l’oggetto User).
Vediamo ora il servizio che ascolta l’evento LoggedInEvent:
@Singleton public class ActionsLogger { public void afterUserLogged(@Observes @Role("USER") LoggedInEvent event) { System.out.println("Logged a USER " + event.getUser()); } public void afterAdminLogged(@Observes @Role("ADMIN") LoggedInEvent event) { System.out.println("Logged the ADMIN " + event.getUser()); } public void afterLoggedAny(@Observes LoggedInEvent event) { System.out.println("Logged with success " + event.getUser()); } }
Come si vede il servizio ActionsLogger definisce tre metodi ascoltatori in questo modo:
- il metodo afterUserLogged(…) ascolta solo il login di utenti non privilegiati;
- il metodo afterAdminLogged(…) ascolta solo il login di utenti amministratori;
- il metodo afterLoggedAny() ascolta qualsiasi evento di tipo LoggedInEvent.
Per verificare le funzionalità di ActionLogger lanciamo il seguente test su Jboss:
@Test public void testService() throws Exception { service.loginUser("moka", "12qwas"); service.loginUser("cafe", "12qwas"); }
Visto che un utente è amministratore solo se si chiama “moka”, allora l’effetto che ci aspettiamo è il seguente:
- il primo evento verrà intercettato da afterAdminLogged(…) e afterLoggedAny(…);
- il secondo evento verrà intercettato da afterUserLogged(…) e afterLoggedAny(…).
Infatti il logger di JBoss appare come di seguito:
23:38:53,807 INFO [org.jboss.web] (MSC service thread 1-4) JBAS018210: Registering web context: /test 23:38:53,821 INFO [org.jboss.as.server] (management-handler-thread - 6) JBAS018559: Deployed "test.war" 23:38:53,936 INFO [stdout] (pool-4-thread-2) Logged with success User [username=moka, password=12qwas, isAdmin=true] 23:38:53,937 INFO [stdout] (pool-4-thread-2) Logged the ADMIN User [username=moka, password=12qwas, isAdmin=true] 23:38:53,938 INFO [stdout] (pool-4-thread-2) Logged a USER User [username=cafe, password=12qwas, isAdmin=false] 23:38:53,940 INFO [stdout] (pool-4-thread-2) Logged with success User [username=cafe, password=12qwas, isAdmin=false] 23:38:53,993 INFO [org.jboss.weld.deployer] (MSC service thread 1-3) JBAS016009: Stopping weld service for deployment test.war
Due ultime note sul meccanismo a eventi di CDI. È possibile aggiungere quanti qualificatori vogliamo a uno stesso evento, ed è possibile quindi selezionare l’evento voluto in base ai qualificatori presenti. Per esempio possiamo definire un metodo ascoltatore con più qualificatori come segue:
public void afterDocumentUpdatedByAdmin(@Observes @Updated @ByAdmin Document doc) { ... }
In questo caso ascolteremo l’evento Document solo se è qualificato contemporaneamente con @ByAdmin e @Updated.
Oltre a ciò è possibile definire dei metodi ascoltatori transazionali, si tratta di ascoltatori che ricevono notifiche di eventi prima o dopo il completamento di una fase transazionale. Un ascoltatore transazionale può ascoltare un evento in una delle seguenti fasi (definite dall’enumerato TransactionPhase):
- BEFORE_COMPLETION;
- AFTER_COMPLETION;
- AFTER_FAILURE;
- AFTER_SUCCESS.
Un esempio di ascoltatore transazionale che riceve un evento solo al termine di una transazione eseguita con successo:
void onDocumentUpdate(@Observes(during=AFTER_SUCCESS) @Updated Document doc) { ... }
Codice di esempio di utilizzo dei pattern
In allegato a questo articolo (menu in alto a destra) troverete un progetto modulare (che si chiama cdi-examples) che mostra come usare i pattern spiegati in questo articolo. Il progetto va compilato con Maven con il comando:
~/Projects/cdi-examples$ mvn package
In questo modo vengono compilati tutti i sotto-progetti presenti:
[INFO] Java EE 6 CDI examples Root Project [INFO] 1. Alternative Example: @Qualifier, @Alternative, InjectionPoint [INFO] 1.1. Alternative Qualifier Example: @Stereotype, @Alternative, beans.xml [INFO] 2. Decorator Example: @Decorator, @Delegate, beans.xml [INFO] 3. Event Example: @Observers, Event [INFO] 4. Interceptor Example: @Interceptor, @AroundInvoke [INFO] 6. Singleton and Disposes Example: @Singleton, @Produces, @Disposes, @Dependent
Per eseguire i test su JBoss, occorre attivare il profilo Arquillan. Nel progetto sono previsti diversi tipi di profilo.
- Profilo di default: compila solamente i sorgenti ed i test ma non li esegue.
- Profilo arq-jbossas-remote: compila sorgenti e test e li esegue in remoto su una instanza di JBoss già attiva tramite Arquillan.
- Profilo arq-jbossas-managed: compila sorgenti e test e lancia una instanza di JBoss, deploya i test e li esegue direttamente tramite Arquillan.
- Profilo open-shift: effettua il deploy su open-shift.
Consiglio di usare sempre il profilo arq-jbossas-remote invece che arq-jbossas-managed, perchè è il più veloce: ogni volta che si esegue il profilo managed, Arquillan accende una nuova instanza di JBoss ad ogni esecuzione di un test. Inoltre lanciare i test in un ambiente JBoss già attivo e preconfigurato ha dei vantaggi notevoli.
Per eseguire i test di un modulo con il profilo arq-jbossas-remote, posizionarsi dentro la directory del modulo e digitare:
~/Projects/cdi-examples/singleton-example$ mvn package -Parq-jbossas-remote
Il log del test è tutto su System.out, quindi il log si leggerà in [stdout] di JBoss:
14:25:47,561 INFO [stdout] (pool-3-thread-6) Message produced using @Preferred qualifier.
Conclusioni
Eccoci arrivati al termine di questa serie di articoli dedicati al nuovo framework CDI. Abbiamo mostrato come CDI rappresenti una standardizzazione moderna dei vari framework IoC presenti sul mercato Java, da Spring a Google Guice.
Principalmente CDI, originariamente chiamato web-beans, è uno standard orientato a semplificare l’uso delle JSF all’interno di un contenitore Java EE. Se questo standard riuscirà ad imporsi sul mercato o meno, è ancora presto per dirlo, mancano ancora tutta una produzione di plugin che possano facilitarne l’integrazione con tecnologie web di terze parti, quali per esempio, noSQL DB (MongoDB, neo4j, Redis…), Android, oppure tutta una serie di estensioni che possano integrare facilmente il proprio sito web con i vari servizi AJAX di LinkedIn, Facebook o Google+. Tutti questi strumenti sono già presenti per esempio con Spring, e questo è indubbiamente un punto a sfavore di CDI.
Un punto invece a favore di CDI è la sua naturale modularità: ogni servizio web basato su CDI è per forza di cose modulare, quindi facilmente testabile ed estendibile. Arquillan, inoltre, rende i moduli CDI testabili semplicemente già sul server di riferimento di test, permettendo cosi di evitare di crearsi da soli un proprio ambiente di test.
Le feature di creazione implementate da CDI sono tutte presenti anche su Spring, ma non è sempre vero il contrario: per esempio su CDI non c’è modo di definire un ordine di creazione e distruzione dei bean (dependsOn), nè tanto meno la possibilità di inizializzare un bean in modalità “lazy”. Inoltre non abbiamo notato alcun meccanismo standard di injecting per le proprietà da file di properties (su Spring invece si può usare l’annotazione @Value tramite “Spring Expression Language”).
Nonostante queste limitazioni, CDI è un ottimo standard IoC modulare che permette di migliorare notevolmente il modello di programmazione degli EJB ereditato dal passato, inoltre anche se un framework affermato come Spring risulta ad oggi ancora superiore, sicuramente CDI è ampiamente migliorabile ed in futuro non mancheranno estensioni e plugin di terze parti (ad oggi esiste solo Seam 3 di JBoss[9], che è un insieme di estensioni di Weld).
Riferimenti
[1] CDI Advocacy
https://sites.google.com/site/cdipojo/
[2] Arquillan
[3] JBoss Weld: sito ufficiale di weld
[4] Apache Open Web Beans
http://openwebbeans.apache.org/owb/index.html
Apache TomEE: un interessantissimo web server che integra assieme
tomcat, spring, Weld, EJB, Hibernate
http://openejb.apache.org/apache-tomee.html
In questo link trovate un buon numero di esempi di come usare TomEE
sia per quanto riguarda CDI che tutte le altre tecnologie che integra
http://openejb.apache.org/examples-trunk/index.html
[5] Caucho Resin CanDI home page
http://www.caucho.com/resin-application-server/candi-java-dependency-injection/
[6] Articolo su InfoQ riguardo la confusione tra DI e CDI
http://www.infoq.com/news/2009/08/dependency-injection-javaee6
[7] CDI Expert Group reference documentation
http://jcp.org/en/jsr/detail?id=299
[8] Ecco un elenco di risorse su internet riguardanti i pattern nel mondo j2ee
Wikipedia sui patterns
http://it.wikipedia.org/wiki/Design_pattern
Erich Gamma: “Design Patterns: Elements of Reusable Object-Oriented Software”
http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612
Core Java EE Design Patterns.
http://corej2eepatterns.com/Patterns2ndEd/
Observer pattern
http://it.wikipedia.org/wiki/Observer_pattern
Decorator pattern
http://en.wikipedia.org/wiki/Decorator_pattern
Interceptor pattern
http://en.wikipedia.org/wiki/Interceptor_pattern
[9] Sito ufficiale di Seam 3
http://seamframework.org/Seam3