Il mese scorso abbiamo introdotto la tecnologia GWT mostrando quali sono le caratteristiche principali di una applicazione scritta con questo framework. Questo mese parliamo di GWT-RPC, il sistema di comunicazione asincrona tramite il quale lo strato client (l‘applicazione JavaScript in esecuzione nel browser) e il server possono comunicare in perfetto stile AJAX.
Invocazione RPC
Come si è avuto modo di vedere in precedenza, un’applicazione GWT è in genere suddivisa in parte server e parte client dove il client verrà trasformato in applicazione Ajax-JavaScript in esecuzione all’interno del browser. La parte server invece diverrà comune codice Java in esecuzione all’interno del web container.
Una delle caratteristiche più interessanti del progetto GWT è la possibilità di eseguire chiamate asincrone fra i due layer (client e server); questo obiettivo si ottiene tramite un sofisticato meccanismo che però maschera ogni dettaglio di comunicazione. Lo schema della GWT-RPC offre infatti una semantica a oggetti (per certi versi simile a RMI) in cui il lato client esegue la propria chiamata verso lo strato server e attende poi la risposta: trattandosi di chiamate Ajax (dove la A sta per asynchronous) l’invocazione avviene in modo slegato dalla possibile risposta. Prima di vedere architetturalmente come ciò sia possibile, procediamo con una breve introduzione pratica.
Dato che la comunicazione viene resa possibile tramite l’implementazione di almeno tre componenti (due interfacce e una classe) si usa convenzionalmente un nome logico per identificare questa triade (nome che poi verrà utilizzato come prefisso per le varie componenti). Per l’esempio proposto, useremo RemoteProvider (il costrutto che andiamo a realizzare lo si può considerare un vero e proprio sistema di service-provider) che corrisponde poi al nome del primo oggetto che andremo a realizzare, ossia l’interfaccia client dove sono pubblicati i metodi invocabili dal client sul server. Per chi ha dimestichezza con RMI/EJB, questa interfaccia potrebbe essere assimilata alla interfaccia remota di un session bean.
Nell’esempio presentato si ipotizza di voler esporre sulla parte server una coppia di metodi per la ricerca di entità di tipo User: il primo restituisce un oggetto singolo, il secondo una lista di UserVO.
Oltre all’elenco dei metodi da esporre, nell’interfaccia RemoteProvider, tramite annotation, troviamo il bind sul nome con il quale la controparte remota sarà invocabile dai componenti client side: sempre in analogia con RMI, potremmo dire che si tratta del nome remoto con cui il session bean viene registrato e pubblicato. In questo caso, dato che la parte server della applicazione viene pubblicata tramite una servlet, tale nome è più assimilabile all’URL di un web service. Ecco il codice relativo a tale interfaccia:
@RemoteServiceRelativePath("RemoteProvider") public interface RemoteProvider extends RemoteService{ UserVO findUsersById(String id); List findUsersByRegionName(String regionName); }
Si noti come la definizione dei metodi non introduce alcuna stranezza ne’ sui parametri di input ne su quelli di risposta che sono qui descritti come all’interno di un comune programma Java.
Il secondo passo è quello di definire l’interfaccia async con la quale i metodi verranno descritti in modalità di invocazione asincrona. Seguendo la naming convention tale interfaccia si chiamerà RemoteProviderAsync:
public interface RemoteProviderAsync { public void findUsersByRegionName(String regionName, AsyncCallback<List> callback); public void findUsersByRegionName (String regionName, AsyncCallback callback); }
In questo caso invece troviamo una prima importante novità: fra i parametri di input dei metodi è infatti scomparso il parametro di ritorno e al suo posto troviamo un ulteriore parametro di input di tipo AsyncCallback; questo oggetto è la chiave di tutto il meccanismo di invocazione asincrona, dato che permette la chiamata nella direzione opposta rispetto alla invocazione del metodo (ovvero dal server verso il cliente), chiamata che verrà eseguita non appena il server avrà terminato la sua esecuzione. Il tutto ovviamente avviene in maniera automatica senza che il programmatore debba preoccuparsi di implementare complesse procedure: è il motore di GWT-RPC che pensa a tutto.
L’ultimo passo da realizzare è quello di implementare la classe server con i metodi di callback, cosa che possiamo vedere nell’esempio che segue:
public interface RemoteProviderImpl extends RemoteServiceServlet implement RemoteProvider{ UserVO findUsersById(String id){ // implementazione del metodo di ricerca dell'utente per id } List findUsersByRegionName(String regionName){ // implementazione del metodo di ricerca degli utenti per nome regione } }
In questo caso notiamo che i metodi nuovamente non introducono alcune peculiarità (quindi senza nessun costrutto relativo alla chiamata in callback), ma che la classe che implementa l’oggetto remoto deve estendere l’interfaccia appena definita oltre a derivare dalla RemoteServiceServlet.
Tale servlet appartiene al framework GWT e funziona come gateway fra il client Ajax e la controparte server side: di fatto rappresenta il punto d’accesso per le chiamate asincrone HTTP da parte del client. Tale servlet verrà mappata quindi su un nome logico che è esattamente quello definito nella interfaccia RemoteProvider tramite l’annotazione
RemoteServiceRelativePath("RemoteProvider")
Nel file web.xml (ricordo che una applicazione GWT è una applicazione web) infatti troviamo la definizione/attivazione della servlet nonche’ il mapping sull’URL-pattern corrispondente:
provider com.mokabyte.gwt.server.RemoteProviderImpl provider /com.mokabyte.gwt.MyGWTProject/RemoteProvider
dove com.mokabyte.gwt.MyGWTProject è il nome del modulo GWT come definito nella puntata precedente della serie
Da un punto di vista architetturale lo schema degli oggetti coinvolti nella invocazione remota è raffigurato nella figura 1.
Figura 1 – Schema architetturale dei componenti alla base di GWT-RPC.
Il punto di vista del client
Quanto visto fino a questo punto ci ha permesso di capire l’infrastruttura di base di un sistema RPC, ma non ci dice nulla su come poi si debba realizzare l’invocazione, cosa che invece vedremo adesso. Supponiamo che nel client si voglia ricavare l’elenco degli utenti per poter popolare un qualche componente grafico, come una lista o una griglia (tralasciamo per il momento la parte di definizione dei componenti databound, ossia della associazione della lista di UserVO al widget grafico, cosa che vedremo in seguito).
// crea il reference remoto per l'invocazione RPC final RemoteProviderAsync remoteProvider = GWT.create(RemoteProvider.class); AsyncCallback callback = new AsyncCallback() { public void onSuccess(Object result) { System.out.println("la chiamata asincrona è andata bene. Ha ritornato un UserVO:"+result); } public void onFailure(Throwable ex) { System.out.println("la chiamata asincrona ha provocato un errore "+ex); } }; System.out.println("Invocazione del metodo remoto findUsersById ()"); remoteProvider.findUsersById (id, callback);
Benche’ non sia particolarmente complesso, il codice riportato potrebbe apparire astruso per chi per la prima volta si trova a lavorare con chiamate asincrone e innerclasses. Partiamo dal fondo, ovvero dalla chiamata del metodo findUsersById() al quale viene passato il parametro di ricerca (id dell’utente da ricercare) e una istanza di callback; tale oggetto viene definito poco sopra in modalità inner (anche se per la precisione in questo caso non si tratta formalmente del costrutto di inner class) ed è il punto tramite il quale possiamo gestire la risposta dal server; visto che non ci è dato di sapere se la risposta sarà positiva o con fallimento (per una qualsiasi eccezione rilanciata dal server), l’oggetto di callback predispone due metodi che verranno invocati in modalità asincrona in caso di risposta positiva o negativa. Notare che il metodo onSuccess() riceve un oggetto generico che verrà automaticamente convertito al tipo del parametro di ritorno dell’invocazione remota.
Infine si noti l’esecuzione del metodo GWT.create() necessario per creare una istanza dell’oggetto remoto.
List findUserByRoleName(String roleName);
Serializzazione dei parametri
Una delle caratteristiche che è di fondamentale importanza comprendere (e che a mio modesto parere non è sufficientemente enfatizzata sui vari tutorial GWT) è il meccanismo di serializzazione alla base di RPC-GWT.
Per essere invocato da remoto, ogni metodo esposto deve obbligatoriamente restituire un oggetto della gerarchia client in modo che sia garantita la trasformazione in oggetto JavaScript. Ricordo a tal proposito che un progetto GWT prevede normalmente una suddivisione dei sorgenti in almeno due alberature, la client e la server (si veda [MOD]). Mentre la seconda non aggiunge nulla di particolarmente differente rispetto a un comune progetto Java, la prima è essenziale perche’ contiene tutte quelle classi che potranno essere trasformate in oggetti JavaScript per l’esecuzione nel browser nonche’ per lo scambio di dati fra server e client (in pratica una specie di DTO). Normalmente nel file descrittore del modulo GWT (il file che termina con il suffisso .gwt.xml), tramite l’attributo “source”, si può specificare la directory radice per l’alberatura delle classi client; normalmente la directory usata per le classi traducibili è la client
Durante la fase di compilazione il sistema genera per tali classi una specie di lasciapassare che “autorizza” l’esecuzione di tale codice sul client. Tali autorizzazioni sono poi salvate in un file il cui nome è un codice hash come
33EE5D2FB96FEDBDED1F034DDADAE7DA.gwt.rpc
Tale file deve essere deployato nella cartella pubblica dell’applicazione web GWT in modo che sia accessibile alla RemoteServiceServlet tramite la ServletContext.getResource(). La sua assenza impedisce la serializzazione degli oggetti anche se questi implementano l’interfaccia Serializable.
Mi sono imbattuto in un errore, che pare sia dovuto a un bug: ufficialmente sarebbe chiuso ma al momento della scrittura dell’articolo non ho ancora avuto modo di verificare con certezza tale chiusura dato che mi si è ripresentato fra i piedi; per cui temo che non sia un bug ma una dimenticanza. Tale errore è legato alla impossibilità da parte del sistema di procedere alla serializzazione di oggetti composti e usati come parametri di risposta.
Se infatti il metodoXYZ() restituisce una istanza della ClasseA, avremo nelle interfacce di cui sopra e nella classe implementazione della parte server:
public ClasseA metodoXYZ() {}
quindi automaticamente il compilatore procede a creare una autorizzazione per la ClasseA che potrà essere tranquillamente inviata o usata nel client. Nel caso in cui all’interno della ClasseA sia dichiarata una istanza di ClasseB (caso piuttosto frequente se ad esempio sul client dobbiamo visualizzare una qualche struttura master-detail) allora presumibilmente potrebbe accadere che la ClasseB non sia passata per il “verificatore” di serializzazione e che quindi si potrebbe ottenere un errore del tipo:
com.google.gwt.user.client.rpc.SerializationException: Type 'xxx.yyy.zzz.ClasseB' was not included in the set of types which can be serialized by this SerializationPolicy. For security purposes, this type will not be serialized.
Dato che tale classe non potrà essere inviata al client, una soluzione trovata sul wiki di Google (ma battezzata “workaround” quindi più un sistema di evitare il problema che una vera soluzione allo stesso) è quella di pubblicare un metodo fasullo (o comunque non necessario alla corretta esecuzione dell’applicazione) che esponga direttamente ClasseB come parametro di ritorno
public ClasseB metodoKWJ() {}
in questo modo anche ClasseB passerà per il validatore e il token di serializzazione verrà incluso nel file di cui sopra.
Conclusione
Questo mese abbiamo parlato di uno degli aspetti più importanti del framework GWT, ossia il meccanismo di invocazione asincrona RPC. A partire dalle prossime puntate di questa serie vedremo come mettere in pratica questa tecnica ad esempio per popolare componenti grafici nelle varie GUI realizzate con componenti GXT.
Riferimenti
[RPC] Google Code. Tutorial RPC
http://code.google.com/webtoolkit/doc/latest/tutorial/RPC.html
[MOD] Using and creating modules
http://developerlife.com/tutorials/?p=229