Che tecnologia utilizzano applicazioni e servizi di google come GMail, Google Docs e Google Maps?. Alla base di queste web application c’è una tecnologia su cui Google ha iniziato ad investire molti anni fa: Google Web Toolkit (GWT), un framework di sviluppo Java con il quale è possibile scrivere applicazioni AJAX tanto complesse quanto veloci, mantenendo allo stesso tempo una fortissima compatibilità verso gli standard web.
AJAX, la colonna portante di GWT
In tempi "non sospetti" Google ha pensato bene di trovare una risposta ad un tedioso problema: il tempo speso dagli sviluppatori web per risolvere problemi di incompatibilità legati ai browser, ed alla natura poco modulare del linguaggio JavaScript.
Il linguaggio JavaScript, benché ricco di ottime librerie che aiutano a sviluppare web applications (JQuery, Dojo, Scriptaculous, YUI e tante altre), può porre infatti dei forti limiti agli sviluppatori che vogliano rendere la loro web application scalabile in termini di dimensioni e complessità. Se aggiungiamo a questo limite la scarsa disponibilità di tool di sviluppo e di debugging, rispetto ai tool disponibili sui linguaggi di programmazione di larga diffusione, viene facile pensare quanto caro possa costare lo sviluppo di un'applicazione scritta interamente in JavaScript.
D’altro canto, lo stesso linguaggio JavaScript, coniugato alle funzionalità legate alle chiamate HTTP asincrone (XMLHttpRequest in particolare), ha dimostrato quanto fosse solida la tecnica di sviluppo AJAX e di manipolazione del DOM.
La nostra applicazione web è scritta in Java ed il toolkit di Google la traduce (più avanti vedremo come...) in codice JavaScript e HTML, generando una soluzione AJAX altamente aderente agli standard web, consolidata e compatibile con tutti i browser.
Lo sviluppatore può quindi scrivere la propria web application con il suo IDE preferito (Eclipse, NetBeans o altro), fare debug in ogni singolo punto dell’applicazione, integrarla con librerie JavaScript di terze parti, testarla, compilarla e pubblicarla sotto forma di file HTML e JavaScript. E’ sufficiente un semplice browser per vedere quale magnifico risultato possiamo ottenere con un toolkit di tale potenza.
Fortissima interoperabilità e amore verso gli standard web: in questo modo Google è riuscita a trovare una soluzione brillante che tanti altri competitor (Adobe, Microsoft, Oracle ed altri) sono riusciti a risolvere con soluzioni proprietarie ben lontane dalle specifiche del W3C.
Il motore GWT
Come abbiamo detto, GWT fornisce una serie di strumenti pensati per realizzare web applications complesse in grado di girare su un qualsiasi browser moderno. Partendo da questa premessa e pensando a GWT, i progettisti di Google hanno cercato di seguire queste linee guida:
- Programmare il codice client (UI inclusa) in linguaggio Java.
- Tradurre il codice Java in codice JavaScript.
- Mantenere la compatibilità con i framework JavaScript e CSS di terze parti.
- Programmare la comunicazione client/server con il minimo sforzo, mantendendo una forte compatibilità verso JSON e XML, così da garantire l’integrazione con server di qualunque tipo (php, ruby, java, etc.).
- Facilitare la programmazione del codice server facendo leva sullo standard Java delle Servlet.
- Fornire un framework in grado di facilitare la programmazione delle Rich Internet Applications (Widget, Pannelli, Internazionalizzazione, comunicazione RPC, History Management, etc.).
Da questi pochi requisiti è nato il Google Web Toolkit, che è oggi composto da questi strumenti:
- Un compilatore da Java a JavaScript.
- Uno strato di Emulazione JRE (l’ambiente runtime Java), scritto interamente in JavaScript
- Una interfaccia per definire metodi nativi JavaScript (JSNI).
- Le GWT API: una suite di API Java pensate espressamente per l’implementazione di client AJAX.
Se volessimo immaginare ad un semplice processo di sviluppo, potremmo dire che il programmatore sviluppa il suo progetto di web application interamente in Java, con il suo ambiente di sviluppo Java preferito. Il progetto è quindi testato con le JUnit e compilato infine in JavaScript e HTML (si può immaginare facilmente una analogia con la traduzione in bytecode Java).
Quando l'utente aprirà la pagina HTML, all'interno di un ambiente di runtime che emula il JRE, le chiamate avverranno in realtà completamente in javascript. Potremmo dire che questo è il meccanismo di base con il quale GWT è riuscito a coniugare la potenza di Java con la flessibilità dell’ambiente JavaScript.
Nel prossimo paragrafo vedremo più in dettaglio come funzionano singolarmente questi strumenti.
Il compilatore da Java a JavaScript
Il compilatore è la parte centrale del toolkit. Questo tool, un’applicazione java (com.google.gwt.dev.Compiler
) avviabile da linea di comando, è uno strumento che traduce il codice sorgente Java in codice equivalente JavaScript.
L’analogia con il compilatore Java è innegabile: come il compilatore Java (javac) traduce il codice Java in bytecode, così il compilatore GWT produce codice JavaScript. Entrambi analizzano il codice sorgente Java per generare il compilato.
Una classe Java di questo tipo:
package com.html.client;
public class HelloWorld {
String message;
public HelloWorld(String message) {
this.message = message;
}
}
Sarebbe tradotta dal compilatore, omettendo alcuni dettagli, nella seguente forma JavaScript:
function com_html_client_HelloWorld_HelloWorld__Ljava_lang_String(message) {
java_lang_Object_Object.call(this);
this.com_html_client_HelloWorld_message = message;
}
Se leggiamo con attenzione il codice generato, il compilatore crea una forma JavaScript cercando di mantenere gli stessi specificatori di package, gli argomenti dei metodi e le stesse relazioni agli oggetti (i.e. java.lang.Object
come classe base). Un altro particolare, familiare a molti, è la convenzione che usa il compilatore GWT per marcare i metodi, molto simile a quella usata dal compilatore standard Java per definire le “firme” (signature) dei sui metodi.
Il compilatore GWT, così come tutti gli strumenti di sviluppo, è pensato per generare codice JavaScript a diversi livelli di ottimizzazione (10 livelli possibili), debugging, logging e di generazione del codice.
La generazione del codice JavaScript può essere fatta con uno di questi tre stili:
- Offuscato (Obfuscated)
- Formattato (Pretty)
- Dettagliato (Detailed)
Il primo stile, obfuscated, genera il codice JavaScript completamente offuscato: il codice è illeggibile, compatto e di piccole dimensioni, quindi è raccomandato solamente quando pubblichiamo in produzione la nostra applicazione.
Gli altri due stili, pretty e detailed, sono raccomandati invece per la fase di sviluppo: la dimensione dei file generati con queste due modalità è quasi doppia rispetto all’obfuscated. Il detailed in particolare fornisce un livello paragonabile a quello che abbiamo visto nell’esempio di sopra, ricco di specificatori di package e signature complete: questo stile è molto comodo per ricavare informazioni del codice Java dalla console degli errori JavaScript.
Lo strato di emulazione JRE
Dopo aver introdotto nel paragrafo precedente il compilatore che traduce il codice Java in JavaScript, è interessante esaminare anche lo strato JavaScript che gioca il ruolo di JRE (Java Runtime Environment emulation).
Il nostro programma Java, una volta compilato, sarà eseguito in un’ambiente che emulerà tutte le chiamate alle librerie Java (java.lang, java.util
, etc.), i tipi, le eccezioni, le asserzioni e la reflection, la garbage collection, il multi-threading e la sincronizzazione.
Ovviamente non tutto è emulabile e GWT pone dei limiti.
Il linguaggio Java di riferimento è il 5 e l’uso dei generics è consentito.
Possiamo poi usare tutte le librerie che GWT fornisce nel suo strato di emulazione, corrispondenti alle native del JRE ufficiale. Dalle prime versioni di GWT ad oggi, l’emulazione del JRE è andata via via crescendo, la lista seguente mostra il set di package JRE che possiamo usare in GWT:
- java.lang
- java.lang.annotation
- java.math
- java.io
- java.sql
- java.util
- java.util.logging
In pochissimi casi alcuni di questi package potrebbero non implementare delle particolari funzionalità. In altri casi, in mancanza di uno specifico package, Google ha pensato di preparare dei package equivalenti, o di lasciare al programmatore la libertà di implementarli.
Un caso eclatante è quello della classe java.util.DateTimeFormat
: Google ha dichiarato che l’emulazione di questa classe sarebbe stata troppo costosa in termini di elaborazione e di performance per JavaScript. È stata quindi tolta dall'emulazione JRE, e fornita una versione “light” in com.google.gwt.i18n.client.DateTimeFormat
. Seguendo la stessa logica, se dovessimo trovarci di fronte un caso simile, prima di iniziare un’implementazione personale, è bene verificare che Google non abbia già fornito una soluzione alternativa.
Occupiamoci ora delle eccezioni: sono completamente supportate le keyword try
, catch
e finally
. Quando usiamo le eccezioni dobbiamo tenere presente che, ragionando in termini JavaScript, alcune eccezioni come NullPointerException
, StackOverflowError
e OutOfMemoryError
non saranno mai sollevate, perchè strettamente dipendenti dalla natura del linguaggio di programmazione Java e dal suo runtime. Esiste però una controparte, più generica, chiamata JavaScriptException
, usata da GWT per mappare tutte le eccezioni non mappabili in classi corrispondenti Java.
Multithreading e sincronizzazione: il codice JavaScript è eseguito in un ambiente single-thread e GWT non fornisce alcuna classe a supporto del multi-threading. Il compilatore GWT solleverà degli errori se proviamo ad usare metodi come Object.notify()
, Object.wait()
o Object.notifyAll()
. La keywork synchronized
, invece, sarà semplicemente ignorata dal compilatore.
Della garbage collection, GWT non supporta solamente la finalizzazione degli oggetti. Delle funzionalità di reflection, GWT supporta solamente Object.getClass().getName()
.
In linea generale, GWT riesce a fornire una buona parte delle principali funzionalità Java, in una forma JavaScript equivalente. Il programmatore Java, ragionando in termini delle necessità di programmazione per un client AJAX, troverà pochissimi casi in cui sentirà la mancanza di qualcosa rispetto ad un’ambiente runtime Java tradizionale. A completare il quadro delle librerie disponibili non dimentichiamoci che abbiamo la suite di API fornita da GWT, di cui forniremo maggiori dettagli più avanti.
JavaScript Native Interface
Non sempre, usando esclusivamente Java, è semplice realizzare una applicazione web, ad esempio potremmo incorrere a un certo punto nella necessità di scrivere codice JavaScript per gestire un effetto visivo, o utilizzare una specifica libreria (o un plugin) di terze parti.
In casi del genere GWT ci viene in aiuto fornendoci un’interfaccia nativa JavaScript per scrivere codice JavaScript all'interno della nostra classe Java.
JSNI(JavaScript Native Interface) fa uso della keyword Java native, esattamente come per i metodi nativi JNI implementati in C per il linguaggio Java.
Per definire un metodo JavaScript dobbiamo marcarlo come nativo ed inserire il codice JavaScript in una coppia di delimitatori, per chiarirne la semantica al compilatore java. Vediamo un esempio:
public static native String sayHelloToMessage(String message)
/*-{
var new_message;
new_message = "Hello " + message;
return new_message;
}-*/;
Il corpo della funzione sayHelloToMessage() conterrà il codice JavaScript che vogliamo. I delimitatori del codice JavaScript saranno /*-{ per aprire il blocco e }-*/ per chiudere il blocco. Il compilatore tratterà il body tra i delimitatori come codice JavaScript, mentre non trascurerà gli argomenti (nel nostro caso “String message”) che saranno opportunamente compilati in java. Dal metodo così generato è possibile interagire con codice javascript e con oggetti java, salvo rispettare le convenzioni definite da Google.
Le API di GWT
In aggiunta ai vari componenti visti finora vengono fornite naturalmente anche delle api per semplificare lo sviluppo della propria Rich Internet Application.
Le API di GWT possono essere facilmente raggruppate in queste categorie:
- Widgets e Panels. Queste sono le API fornite per disegnare le nostre interfacce utente, con una architettura delle UI molto simile a Swing, pur nei limiti imposti dagli elementi HTML.
La UI è composta infatti da elementi (widget) e da contenitori (panel) che li contengono. I widget sono interattivi e possono rappresentare ad esempio bottoni, checkbox, tabelle e cosi via.
I panel sono pensati per disporre automaticamente i widget in un’area della pagina HTML, con gli usuali layout verticali, orizzontali, flow (la disposizione HTML predefinita) ed altri ancora.
Alcuni pannelli infine possono anche essere interattivi e sono in grado sia di contenere widget che di rispondere a degli eventi di UI (ad esempio i TabPanel). - I18I. Questo sono le API per fornire la localizzazione e internazionalizzazione alla nostra applicazione. GWT fornisce le API per definire sia staticamente che dinamicamente la localizzazione delle risorse di testo.
- Request Builder, JSON e XML Parser. Queste API si occupano di facilitare la comunicazione con i server che espongono servizi in XML e JSON. Si occupano di costruire le richieste al server e poi di processare i dati di risposta.
- RPC. Se vogliamo comunicare con un server Java, GWT ha specificato un protocollo RPC che possiamo usare per implementare un servizio client/server con poche linee di codice. Queste API sollevano il programmatore dalla gestione della comunicazione e dal processo di marshalling/unmarshalling dei dati.
- History Management. Le applicazioni AJAX generate da GWT sono costruite su di una singola pagina HTML. Queste API offrono uno strumento, compatibile con il browser, per definire le regole di historing e di navigazione all’interno della stessa pagina HTML (vedi GMail).
- Integrazione con JUnit. Queste API discendono direttamente dalle best practice per gli Unit Test, e servono all'integrazione con JUnit.
Nella prossima parte dell'articolo metteremo in pratica le cose viste finora, sviluppando un semplice esempio.
Creazione di un progetto con GWT
Dopo aver parlato di GWT da un punto di vista teorico per avere una visione di insieme, ci preoccuperemo ora della creazione di un primo progetto.
Dove prendere l’SDK di GWT
L’SDK è disponibile sul sito di Google Code a questo indirizzo:
Dalla lista si può prendere l’ultima versione di SDK (compatibile con le piattaforme Windows, Linux e OS X) disponibile. Il nome del file dovrebbe essere del tipo: gwt-x.y.z.zip, dove x.y.z è la versione di GWT da scaricare (di solito la "Featured").
A questo punto non resta che estrarre il file .zip in una cartella a proprio piacimento ed inserire nel PATH di sistema la directory dell’SDK di GWT. Quest'ultima azione è in realtà opzionale ma consigliabile: in questo modo è possibile usare GWT da qualunque punto del sistema.
Una overview della "catena di montaggio" di sviluppo in GWT
L’SDK contiene tutto il necessario per lavorare in un’ambiente GWT: gli applicativi dell’SDK che più spesso useremo sono il webAppCreator ed il compilatore GWT. Entrambi prendono parte alla tool chain di sviluppo e contribuiscono rispettivamente alla creazione ed alla compilazione del progetto.
Setup dell’ambiente di sviluppo
L’SDK di Google Web Toolkit è studiato per funzionare da linea di comando, e noi seguiremo questo approccio.
I progetti generati dall’SDK sono compatibili con i seguenti ambienti di sviluppo:
- Apache Ant (consigliato per cominciare)
- Apache Maven (compatibile con NetBeans e molto interoperabile)
- Eclipse (.project e .classpath)
Abbiamo scelto per semplicità sia Apache Maven che Apache Ant: Maven dovrà essere installato e reso accessibile anch'esso nel PATH di sistema.
Creazione di un progetto
Dalla shell dei comandi andare nella directory dell’SDK di GWT. In questa cartella dovrebbero essere presenti questi due file eseguibili:
- webAppCreator (script per ambienti Linux/OS X)
- webAppCreator.cmd (script per ambienti Windows)
Per creare un progetto ex novo, bisogna usare uno di questi due script (in base al sistema operativo che usiamo). Il comando, nella forma minimale, richiede il nome del modulo GWT da creare:
webAppCreator com.html.HelloWorld
In questo caso, stiamo chiedendo di creare un progetto per il modulo HelloWorld nel package com.html. Senza altri parametri aggiuntivi, il progetto sarà compatibile con Ant ed Eclipse. Se aggiungiamo il parametro -maven il progetto sarà anche compatibile con Maven.
Se vogliamo generare il progetto in una directory diversa da quella dell’SDK (consigliabile) dovremo chiamare il creator con il parametro -out, specificando la directory dove scrivere il progetto. Esempio:
webAppCreator -out /GWT/HelloWorld com.html.HelloWorld
In questo caso il progetto sarà creato nella directory /GWT/HelloWorld. Il progetto è scaricabile al seguente link
La struttura di un progetto GWT
La struttura del progetto dipende dal tipo di generazione che abbiamo richiesto al webAppCreator. Nel caso di default, senza il parametro -maven, il progetto dovrebbe apparire in questo modo:
Il file build.xml è il file di progetto di Ant, mentre il .classpath
ed il .project
sono quelli usati da Eclipse.
La directory src contiene tutto il codice sorgente del progetto, la parte client, quella server e quella condivisa da entrambi (com.html.shared).
Oltre ai file *.java, la cartella del package di base, com.html
, contiene il file HelloWorld.gwt.xml
. Questo è il file che descrive il modulo GWT ed è usato dal compilatore GWT per avere tutte le informazioni del progetto. Come vedremo più avanti questo è il file più importante di tutto il progetto.
La directory test è usata per salvare gli Unit Test di JUnit.
Infine abbiamo la directory war
. Questa directory conterrà il codice compilato HTML e JavaScript e, nel caso di componenti server scritte in Java, il file .WAR compatibile con un qualsiasi servlet container (e.g. Jetty, Tomcat, etc.). Il file HelloWorld.html
è il nostro entry point e fornisce la pagina di ingresso della nostra applicazione.
Per compilare il progetto dobbiamo chiamare ant dalla cartella dove è localizzato il file build.xml.
A compilazione terminata, la cartella war/helloworld del nostro progetto conterrà una serie di file .html, .png e .js. Questi file rappresentano l’output del compilatore GWT.
Avviare un progetto GWT
Un progetto GWT può essere avviato in due modalità
- Development Mode. Da usare nella fase di sviluppo.
- Production Mode. Da usare in produzione, quando pubblichiamo la nostra applicazione sul web.
In development mode, GWT avvia una console grafica che incorpora anche un server minimale (Jetty). Possiamo usare questa console per avviare in automatico il browser, leggere i log del server ed i log generati dal modulo GWT (client). In questa modalità possiamo modificare il codice client senza ricompilare, nè riavviare la console di sviluppo. La modalità di sviluppo è invocabile da Ant in questo modo:
ant devmode
Alla partenza, possiamo quindi cliccare sul bottone “Launch Default Browser” per vedere l’applicazione GWT HelloWorld in azione.
In production mode, GWT semplicemente compila il progetto GWT per la parte client e, se richiesto, per la parte server. La parte client è compilabile chiamando ant (senza parametri aggiuntivi), e se ne può effettuare deploy in un qualsiasi web server. Se invece vogliamo generare un package .war per il deploy in ambienti server Java, il comando da usare è il seguente:
ant war
Alla fine della compilazione avremo:
- Per la parte client (se non necessita di server Java) i file:
- /war/HelloWorld.html
- /war/HelloWorld.css
- /war/helloworld/* (tutti i file)
- Per la parte client+server il file HelloWorld.war (si può utilizzare all'interno di un servlet container)
Task Manager: una applicazione di esempio con GWT
L’idea di base è di costruire una Rich Internet Application che faccia da Task Manager (gestione delle attività) e che sia in grado di reperire dal server una lista dei task. L’applicazione deve apparire sotto forma di un’unica pagina HTML e deve contenere per ogni attività le seguenti voci:
- Fatto o non fatto (se l’attività è stata completata o meno)
- Descrizione dell’attività
- Priorità dell’attività
Ogni attività può essere aggiunta o rimossa dinamicamente. Nella fase di caricamento della pagina HTML, la lista deve essere riempita con i task comunicati dal server.
Passo dopo passo, vediamo come è stata realizzata questa applicazione.
Setup del Task Manager
Il progetto del Task Manager è disponibile al seguente link
La base di questo progetto è stata inizialmente creata con il webAppCreator e successivamente modificata. Il comando che abbiamo usato per generare il progetto è il seguente:
webAppCreator -maven com.html.TaskManager
oppure (in ambiente Windows)
webAppCreator.cmd -maven com.html.TaskManager
Noi faremo riferimento al progetto allegato, pronto per l’uso, per facilitare la consultazione e la spiegazione del codice sorgente.
Inoltre, da questo punto in avanti, faremo riferimento al progetto in ambiente Linux. Per quelli che usano Windows, basta semplicemente riconsiderare i percorsi dei file.
Avviamo il Task Manager
Avviamo il progetto in development mode, dalla directory del progetto digitando nella console
mvn gwt:run
(oppure l’equivalente)
ant devmode
dovrebbe apparire una finestra grafica come questa
Cliccare sul bottone “Launch Default Browser” dal pannello della console grafica di sviluppo. Dal browser dovrebbe apparire una schermata di questo tipo:
... e questo è il nostro TaskManager, sviluppato interamente in GWT.
Le attività inizialmente presenti sono generate causalmente dal server, come esempio. Dalla pagina HTML possiamo modificare le attività, aggiungerne di nuove o cancellare quelle che non ci interessano più.
Il file di configurazione del modulo GWT
Per capire questo progetto, è necessario partire dal file di configurazione del modulo. Andiamo nella directory /src/com/html
ed apriamo il file TaskManager.gwt.xml
. Il file dovrebbe mostrarvi questo contenuto (escludendo i commenti):
<module rename-to='taskmanager'>
<inherits name='com.google.gwt.user.User' />
<inherits name='com.google.gwt.user.theme.standard.Standard' />
<entry-point class='com.html.client.TaskManager' />
<source path='client' />
<source path='shared' />
</module>
Questo file XML descrive il nostro progetto GWT. Il compilatore userà questo file per ricavare tutte le configurazioni del progetto. Vediamone i dettagli:
- L’elemento <module> definisce un modulo GWT. Il nome di un modulo è identificato per via di uno di questi due criteri:
- Usando come riferimento l’attributo rename-to, così come nel nostro esempio (i.e. taskmanager).
- Se l’attributo rename-to non è specificato, allora il modulo prenderà il nome del file .gwt.xml di configurazione (i.e. TaskManager).
- Gli elementi <inherits> definiscono i moduli GWT da cui il nostro codice client dipende. Usa sorta di import di package di libreria.
- L’elemento <entry-point> definisce la classe di ingresso del nostro modulo GWT (i.e. TaskManager.java) e gli elementi <source> definiscono i sub-package, a partire dal package di base (com.html), nei quali prendere i file da compilare in JavaScript.
Se ad esempio abbiamo:
<source path='client' />
Il compilatore GWT cercherà i file .java nella directory /com/html/client
.
Il file che abbiamo appena visto contiene la configurazione di default. Non abbiamo bisogno di apportare modifiche ed useremo questa configurazione per l’intero progetto.
La classe di EntryPoint
La classe di entry point definita nel file di configurazione del modulo
<entry-point class='com.html.client.TaskManager' />
rappresenta l’ingresso della nostra applicazione client GWT. Questa classe Java, presente in /src/com/html/client/
, deve implementare l’interfaccia GWT EntryPoint che definisce come unico metodo onModuleLoad()
.
Il motore GWT, in fase di bootstrap, instanzierà la classe TaskManager e chiamerà il metodo che noi abbiamo implementato di questa interfaccia.
Questo è il punto dove inseriamo il codice di inizializzazione dell’applicazione.
L’applicazione client e lo sviluppo della UI
Il cuore di tutta l’applicazione client è implementata nella classe TaskManager.java. In testa alla classe troveremo la parte riguardante l’inizializzazione dei widget e dei panel:
public class TaskManager implements EntryPoint {
final VerticalPanel verticalPanel = new VerticalPanel();
final Label labelCaption = new Label("GWT Task Manager");
final FlexTable flexTableTasks = new FlexTable();
final Button buttonAddNewTask = new Button("Aggiungi nuova attività");
public void onModuleLoad() {
...
}
...
}
Il VerticalPanel è il contenitore principale del nostro Task Manager. La caratteristica di questo pannello è la capacità di disporre i widget contenuti in una singola colonna, uno sotto l’altro. Noi lo useremo per contenere i seguenti widget:
- Label. Questo widget inserisce un testo.
- FlexTable. Questo widget inserisce una tabella che può variare il numero di colonne e di righe in maniera dinamica. All’interno di ogni cella è possibile inserire altri widget.
- Button. Questo widget rappresenta un bottone.
Se prendiamo il contenuto della pagina HTML, possiamo immaginare il panel ed i widget disposti in questa maniera:
In termini di oggetti, un’applicazione GWT è sempre composta da oggetti che discendono da Widget. Il Panel è una particolare specializzazione di Widget che può contenere altri Widget.
La strategia usata per disegnare la UI è la stessa che usiamo per scrivere pagine in HTML.
L’oggetto in analogia al body di una pagina HTML è chiamato RootPanel: per inserire widget ed altri panel alla pagina HTML, dobbiamo ottenere un riferimento al pannello ed inserire in esso gli elementi (widget o panel) che desideriamo.
Tornando al codice, nell’implementazione di onModuleLoad()
, entry point dell’applicazione client, possiamo notare:
public void onModuleLoad() {
...
RootPanel rootPanel = RootPanel.get();
// Aggiungiamo il VerticalPanel alla pagina principale
rootPanel.add(verticalPanel, -1, -1);
...
}
In questo punto stiamo chiedendo a GWT di avere il root panel (i.e. il body dell’HTML) e di aggiungere, come primo elemento, un VerticalPanel. A seguire inseriamo i widget all’interno del VerticalPanel in questa maniera:
public void onModuleLoad() {
...
// Aggiungiamo al pannello la Label
verticalPanel.add(labelCaption)
...
// Aggiungiamo al pannello la tabella dinamica (FlexTable)
verticalPanel.add(flexTableTasks);
...
// Aggiungiamo al pannello il bottone
verticalPanel.add(buttonAddNewTask);
...
}
Tutti i widget ed i pannelli inseriti nel RootPanel sono visualizzati nella pagina finale HTML.
Tornando all’analogia con l’HTML, così come possiamo caratterizare un elemento HTML (e.g. table, cella, bottone, etc.), ogni widget può avere degli attributi e degli stili CSS associati.
Questo frammento ci mostra come fare:
public void onModuleLoad() {
...
// Impostiamo alcune proprietà del pannello
verticalPanel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
verticalPanel.setVerticalAlignment(VerticalPanel.ALIGN_TOP);
verticalPanel.setWidth(String.valueOf(Window.getClientWidth()) + "px");
verticalPanel.setSpacing(5);
...
}
In pratica, stiamo chiedendo a GWT di inizializzare il pannello come se dovessimo definire degli attributi di una table (i.e. il VerticalPanel è in effetti una <table> HTML). Oppure:
labelCaption.setStyleName("flex-table-caption");
per dire a GWT di associare la classe di stile CSS flex-table-caption alla label.
Abbiamo visto in questo paragrafo come disegnare dinamicamente una UI per via programmatica, ma è possibile anche definire elementi HTML staticamente (file .html), per poi manipolarli dinamicamente tramite i loro id associati in questo modo:
RootPanel rootPanel = RootPanel.get("mio_div");
dove mio_div è l’id che abbiamo assegnato all’elemento HTML.
L’interazione con l’applicazione
Alla nostra UI dobbiamo adesso associare una gestione degli eventi. In GWT, molto similmente a Swing e jQuery (volendo fare un confronto con JavaScript), possiamo associare ad ogni widget una classe che risponde ad uno o più eventi. In Java questo tipo di classi sono denominati handler.
Le API di GWT ne offrono una varietà pari alle stesse che potremmo definire in JavaScript. Troviamo infatti le classi BlurEvent
, ChangeEvent
, ClickEvent
, MouseOverEvent
, e così via.
Nella nostra applicazione di Task Manager, per associare un click handler al bottone “Aggiungi una nuova attività”, abbiamo scritto questo frammento di codice:
// Associamo un'handler per gestire il click del bottone
buttonAddNewTask.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
// Quando il bottone è premuto aggiungiamo un nuovo Task
Task task = new Task();
task.setDone(false);
task.setDescription("");
task.setPriority(0);
insertNewTask(task);
}
});
Il codice aggiunge una nostra implementazione di ClickHandler
al widget buttonAddNewTask
(widget Button). Il metodo onClick()
del nostro handler è chiamato ogni qualvolta viene premuto il bottone.
L’applicazione server
In precendenza abbiamo detto che l’applicazione Task Manager, in fase di startup, chiederà al server una lista di task da presentare all’utente. Il nostro client quindi aprirà una comunicazione con il server, richiederà i dati e li presenterà, una volta ricevuti, nella pagina HTML.
Il nostro servizio server è implementato con il protocollo RPC definito da GWT, una soluzione praticabile per chi desidera esportare il servizio in una servlet Java.
Nel caso del Task Manager, l’implementazione finale è questa:
public class TaskStoreServiceImpl
extends RemoteServiceServlet implements TaskStoreService {
static final String taskDescriptions[] = {...};
@Override
public List<Task> getTasks() {
List<Task> list = new ArrayList<Task>();
int base = (int)(Math.random() * 5.0);
for(int i = 0; i < 4; i++) {
Task task = new Task();
task.setDone((int)(Math.random() * 2) == 1);
task.setDescription(taskDescriptions[base + i]);
task.setPriority((int)(Math.random() * 3));
list.add(task);
}
return list;
}
}
Come si può notare, il codice è molto compatto: in GWT, usando il protocollo RPC, il programmatore è chiamato semplicemente ad implementare la funzione legata al servizio erogato. L'aspetto che colpisce di più è l'assenza di serializzazione, deserializzazione, endpoints, query parameters ed altre cose simili.
Questi sono i passaggi richiesti per scrivere un servizio RPC:
1) Definire un’interfaccia Java che estende RemoteService. Il file dovrà essere messo in uno dei package del client. Facendo riferimento al progetto Task Manager, il file TaskStoreService.java
in /src/com/html/client
contiene questo codice:
@RemoteServiceRelativePath("taskStoreService")
public interface TaskStoreService extends RemoteService {
public List<Task> getTasks();
}
L’annotation @RemoteServiceRelativePath
definisce il nome del servizio (taskStoreService). Questo nome è un riferimento che, usato insieme al nome del modulo GWT (taskmanager), definisce la URI alla quale trovare il servizio. Volendo fare un’esempio. Supponiamo di avere il nostro Task Manager sul sito
http://localhost:8080/TaskManager.html
In fase di caricamento, la nostra applicazione client (la componente JavaScript della pagina HTML), chiamerà il servizio remoto all’indirizzo:
http://localhost:8080/taskmanager/taskStoreService
dove taskmanager è il nome del modulo GWT e taskStoreService il nome assegnato nell’annotation. Volendo generalizzare questa convenzione, potremmo scriverla in questa maniera:
http://<host>/<nome-del-modulo-gwt>/<nome-del-servizio-rpc>
2) Definire un’interfaccia di servizio asincrono corrispondente a quella annotata con @RemoteServiceRelativePath
. Nel nostro caso, abbiamo definito in /src/com/html/client
, nel file TaskStoreServiceAsync.java
, queste righe di codice:
public interface TaskStoreServiceAsync {
public void getTasks(AsyncCallback<List<Task>> callback);
}
Questa interfaccia sarà usata dal client per effettuare le chiamate asincrone verso il server. L’interfaccia dovrà avere lo stesso nome del servizio seguito dal suffisso Async. Inoltre, dovrà definire gli stessi metodi del servizio da cui dipende in un modo un pochino particolare.
Ogni metodo di quesa interfaccia dovrà ritornare void e dovrà avere come unico argomento un tipo AsyncCallback<T>, dove T è il tipo di ritorno del metodo descritto nel servizio. Nel nostro caso, se abbiamo nell’interfaccia di servizio questo metodo:
public List<Task> getTasks();
allora il metodo asincrono corrispondente sarà:
public void getTasks(AsyncCallback<List<Task>> callback);
3) Terzo ed ultimo passaggio richiesto per scrivere un servizio RPC è l’implementazione, lato server, dell’interfaccia annotata @RemoteServiceRelativePath
.
Fatto ciò, abbiamo tutto l’occorrente per abilitare il client alla comunicazione con il server.
La comunicazione RPC
Il codice client userà l’interfaccia asincrona del servizio RPC per comunicare con il server. Anche su questo fronte, il codice implementato è limitato a poche righe:
public class TaskManager implements EntryPoint {
TaskStoreServiceAsync taskStoreService = (TaskStoreServiceAsync) GWT.create(TaskStoreService.class);
...
public void loadTaskFromServer() {
taskStoreService.getTasks(new AsyncCallback<List<Task>>() {
@Override
public void onFailure(Throwable caught) {
// fa qualcosa...
}
@Override
public void onSuccess(List<Task> result) {
// fa qualcosa...
}
});
}
}
L’implementazione client crea il servizio RPC con il metodo GWT.create():
TaskStoreServiceAsync taskStoreService =
(TaskStoreServiceAsync) GWT.create(TaskStoreService.class);
specificando la classe dell’interfaccia del servizio TaskStoreService.class
. Il metodo GWT.create()
ci restituisce un’istanza dell’interfaccia asincrona, che possiamo usare -quando vogliamo- per chiamare i metodi del servizio RPC.
Ogni volta che abbiamo bisogno di chiamare un servizio remoto, ci dobbiamo ricordare di usare un’istanza di AsyncCallback. Questa classe richiede l’implementazione di due metodi:
- onFailure(), chiamato da GWT quando il client riscontra un errore di comunicazione
- onSuccess(), chiamato da GWT quando il server restituisce i dati della richiesta
Conclusioni
Google Web Toolkit non si limita a questo, riuscirà a sorpredere gli sviluppatori sotto tanti altri aspetti. Lo sviluppo delle applicazioni è facilitato dai plug-in di Eclipse. Le User Interface possono essere sviluppate con i noti framework di GWT-Ext e GWT-Smart (controparte GWT di quelle JavaScript) e le UI possono essere direttamente disegnate da ambienti grafici. Ci sono strumenti per migliorare le performance delle applicazioni e strumenti per integrarsi all’ambiente cloud di Google App Engine.
Sono passati 5 anni dalla prima release ufficiale di Google Web Toolkit ed oggi sta per essere rilasciata la versione 2.3 con il supporto all’HTML5.
La filosofia che sposa semplicità e potenza, keyword che hanno illuminato il noto motore di ricerca, è stata applicata con successo al mondo dello sviluppo web. Google non rimane a guardare e spinge la propria visione ad un futuro dove le piattaforme applicative saranno i browser Internet.