In questo articolo cominciamo a vedere in che modo Wicket, un Java framework a componenti per applicazioni web, possa integrarsi con alcuni dei più diffusi framework Java, quali Spring, iBatis e Hibernate.
Dopo aver illustrato in dettaglio l’architettura e i principali componenti del framework Wicket nei precedenti articoli, cominciamo con questo a descrivere l’integrazione di Wicket con altri framework all’interno di un’applicazione Java web based e multilayer.
La versione di Wicket a cui faremo riferimento è la 1.4-m3. A partire dalla versione 1.4 Wicket richiede necessariamente Java 1.5 o release successiva.
Spring
Il primo framework che prenderemo in esame è Spring (http://www.springframework.org/). Spring è un framework che implementa il pattern IoC (Inversion of Control, detto anche Dependency Injection). In breve: l’IoC consente di descrivere come devono essere valorizzati gli oggetti di una applicazione e con quali altri oggetti hanno delle dipendenze. Il container messo a disposizione dal framework, di tipo lightweight (leggero, in contrapposizione ad altri pesanti, come, per esempio, quelli per gli EJB) è responsabile del collegamento fra gli oggetti ed è lui stesso che “inietta” le dipendenze fra questi ultimi. Così non è necessario implementare classi che devono eseguire delle lookup per trovare i servizi, visto che questi vengono resi disponibili all’interno del container stesso. Per avere maggiori dettagli su Spring, consiglio la lettura della serie di articoli di Mario Casari sull’argomento pubblicati in passato su Mokabyte [3]. Per quanto riguarda invece il pattern IoC, consiglio la lettura dell’articolo “A beginners guide to Dependency Injection” di Dhananjay Nene, pubblicato su The Server Side [4].
La release di Spring a cui faremo riferimento in questi articoli è la 2.5.6.
Diverse strategie di approccio a Spring
Ricorreremo all’ausilio dell’applicazione di esempio implementata per gli articoli precedenti per mostrare come vengono applicati nella pratica i concetti illustrati in questo articolo.
In ambito Wicket, qualora si vogliano iniettare le dipendenze da un container IoC, le maggiori difficoltà che si incontrano sono dovute al fatto che Wicket è un framework unmanaged (cioè non gestisce il ciclo di vita dei suoi componenti) e che i suoi componenti e modelli sono quasi sempre serializzabili. Essere un framework unmanaged, vuol dire che in Wicket è possibile creare una pagina o un componente in qualsiasi punto di una applicazione, usando semplicemente l’operatore new. È chiaro che in questo modo risulta difficile iniettare delle dipendenze perche’ non è agevole intercettare la creazione dei componenti. Una possibile soluzione potrebbe essere il ricorso a una classe factory di tipo Singleton a cui demandare la creazione dei componenti e iniettare a questi le dipendenze in un secondo momento. Però questo tipo di approccio risulta poco flessibile: non va bene nel caso (molto frequente) in cui sia necessario avere, per una pagina o un componente, più di un costruttore, oltre a quello di default. Vediamo ora che problemi comporta l’altro aspetto, cioè quello della della serializzazione. Wicket mantiene il suo albero dei componenti in sessione. In un ambiente clusterizzato, i dati in sessione necessitano di essere replicati attraverso il cluster: questa operazione avviene serializzando gli oggetti nella sessione di un nodo del cluster e successivamente deserializzandoli nella sessione di un altro nodo. Questo è un grosso problema per la dependency injection: le dipendenze hanno quasi sempre riferimenti ad altre dipendenze all’interno del loro container. Quindi c’è il rischio che la serializzazione delle dipendenze di un oggetto, anche se sono poche, provochi la serializzazione in cascata di altre dipendenze, fino al caso limite di serializzazione dell’intero container. Così, al termine del processo di deserializzazione, le dipendenze non faranno più parte del container originario, ma di un suo clone standalone.
Possiamo evitare questi problemi ricorrendo ad una delle seguenti tre strategie:
- Application Object Approach
- Proxy-based Approach
- Annotation-based Approach
Application Object Approach
Come abbiamo visto negli articoli precedenti, ogni applicazione Wicket ha un classe che estende org.apache.wicket.protocol.http.WebApplication. L’unica istanza di tale classe viene creata una volta soltanto e non è mai serializzata. Nel caso di applicazione clusterizzata rimane la stessa attraverso tutti i nodi del cluster: questa caratteristica la rende un’ottima candidata per poter essere presa in considerazione come service locator. Le estensioni Spring di Wicket (wicket-spring.jar) forniscono una classe factory, org.apache.wicket.spring.SpringWebApplicationFactory, per la creazione di oggetti di tipo WebApplication. Tale factory, invece di creare una istanza di WebApplication, la recupera dallo Spring context applicativo. Wicket mantiene l’istanza di WebApplication in una variabile interna ed espone i metodi di helper dedicati, in modo che dai componenti si possano recuperare agevolmente le varie dipendenze.
Vediamo quindi come cambia l’applicazione di esempio passando a Spring.
Nel web deployment descriptor (web.xml) dobbiamo passare come parametro alla WicketServlet non più la classe che estende WebApplication, ma la SpringWebApplicationFactory:
WicketExamples3Application org.apache.wicket.protocol.http.WicketServlet applicationFactoryClassName org.apache.wicket.spring.SpringWebApplicationFactory 1
Bisogna inoltre definire il Listener specifico per le Spring Web Application:
org.springframework.web.context.ContextLoaderListener
e specificare il context file applicativo:
contextConfigLocation /WEB-INF/context.xml
All’interno di context.xml troveremo la definizione della Wicket WebApplication come bean:
Negli articoli precedenti abbiamo implementato una classe dummy (ParticipantManager) per simulare l’accesso ai dati (l’intento principale era quello di focalizzare l’attenzione sullo strato di presentation). In questo articolo e nei prossimi, invece, arriveremo a implementare una applicazione multilayer completa e adotteremo il pattern DAO [5] per quanto riguarda la logica di persistenza. L’implementazione della WebApplication prevederà quindi un attributo di tipo ParticipantDao e i corrispondenti metodi helper:
public class WicketExamples3Application extends WebApplication { private ParticipantDao participantDao; public void setParticipantDao(ParticipantDao participantDao) { this.participantDao=participantDao; } public ParticipantDao getParticipantDao() { return participantDao; } ...
All’interno delle WebPage in cui è necessario accedere ai dati relativi ai partecipanti, l’accesso al DAO specifico avverrà in questo modo:
ParticipantDao getParticipantDao() { return ((WicketExamples3Application)getApplication()).getParticipantDao(); }
Questa prima strategia è la più semplice fra le tre descritte in questo articolo (ed è quella nella maggior parte dei casi va più che bene), consente di evitare i problemi legati alla serializzazione descritti in precedenza, ma può presentare lo svantaggio di dover implementare una WebApplication class molto ingombrante se le dipendenze sono tante.
Proxy-based approach
Un’alternativa alla strategia presentata al punto precedente è costituita dall’implementazione di un proxy dinamico per le dipendenze, che può essere serializzato e deserializzato senza i problemi di cui sopra. Tale proxy deve contenere giusto le informazioni necessarie per il lookup delle dipendenze ed essere quanto più leggero possibile per non appesantire troppo la sessione. L’implementazione è semplice:
public class InitializationProxy implements InvocationHandler { private transient target; public Object invocationHandler(Object proxy, Method method, Object[] args) { if (target==null) { target=lookupTarget(); } return method.invoke(target, args); } }
Implementa l’interfaccia java.lang.reflect.InvocationHandler e ha bisogno solo di sapere qual è l’implementazione del metodo lookupTarget(). In questo caso la factory class (InitializationProxyFactor) genera istanze di InitializationProxy. Rispetto alla strategia precedente, nella WebApplication non abbiamo bisogno di aggiungere gli attributi corrispondenti alle dipendenze definite nel context file applicativo e i rispettivi metodi helper. All’interno delle WebPage in cui è necessario accedere ad esse, il codice sarà il seguente (facciamo sempre riferimento alla solita cara applicazione di esempio):
private ParticipantDao participantDao = InitializationProxyFactory.createProxy(ParticipantDao.class, new IProxyTargetLocator() { public Object locateProxyTarget() { return ( (WicketExamples3Application)Application.get()) .getSpringContext().getBean("participantDao"); } } }
Con questa seconda strategia abbiamo eliminato il problema che può presentarsi nel caso dell’Application Object Approach, ma potremmo avere lo svantaggio di una eccessiva verbosità, poiche’ per ogni dipendenza bisogna creare un proxy e un object locator.
Annotation-based approach
Per superare il problema della verbosità eccessiva si può ricorrere alle annotation, in modo da ottenere l’injection delle dipendenze al momento della costruzione. Per poter fare ciò bisogna aggiungere un’istanza di org.apache.wicket.spring.injection.annot.SpringComponentInjector all’applicazione. Nell’inizializzazione della WebApplication si avrà quindi:
public class WicketExamples3Application extends WebApplication { public void init() { super.init(); addComponentInstantiationListener(new SpringComponentInjector(this)); } ... }
All’interno delle WebPage in cui è necessario accedere ad una o più dipendenze, il codice con le annotations sarà il seguente:
public class RegistrationPage extends WebPage { ... @SpringBean private ParticipantDao participantDao; public RegistrationPage() { init(null); } public RegistrationPage(PageParameters pageParameters) { init(pageParameters); } ... }
In questo modo le dipendenze vengono iniettate nel momento in cui una istanza della WebPage viene creata. La stessa cosa vale per i componenti.
Conclusioni
Abbiamo presentato l’integrazione tra Spring e Wicket ed è stata solo accennata l’implementazione dello strato di persistenza e accesso ai dati. Nella prossima parte vedremo in dettaglio come essa può essere realizzata nella pratica ricorrendo a framework specifici come Hibernate o iBatis. I sorgenti completi dell’applicazione di esempio verranno resi disponibili per il download il prossimo mese.
Riferimenti
[1] Karthik Gurumurthy, “Pro Wicket”, Apress, 2006
[2] Sito ufficiale di Wicket presso Apache
[3] Mario Casari, serie di articoli su Spring pubblicati su Mokabyte, a partire da
https://www.mokabyte.it/cms/article.run?articleId=4UB-OL6-QZG-5XM_7f000001_30520983_38fb6a1b
[4] Dhananjay Nene, “A beginners guide to Dependency Injection”, The ServerSide.com
[5] S. Rossini – L. Dozio, “Il pattern Data Access Object”, MokaByte 62, Aprile 2002
https://www.mokabyte.it/2002/04/pattern-dao.htm
[6] Rod Johnson, “J2EE Development without EJB”, Wrox, 2004