Aplicaciones web 100% Java con Google Web Toolkit
lunes, octubre 2, 2006 at 2:00AM Aplicaciones web 100% Java con Google Web Toolkit (GWT)
Aplicaciones web 100% Java con Google Web Toolkit (GWT)
ÓSCAR ARANDA CRESPO (Arquitecto Java EE)
Google acaba de lanzar un nuevo framework que cambia por completo el modelo de desarrollo tradicional de aplicaciones web. Este framework propone que el desarrollo de las aplicaciones se realice al 100% en Java, sin necesidad de usar tecnologías como HTML o JavaScript.
Introducción
Desde la aparición de AJAX (Asynchronous JavaScript and XML) se está produciendo una revolución en el mundo del desarrollo web. AJAX está llevando al lado cliente mucha de la lógica de aplicación que hasta ahora sólo podía residir en el servidor, consiguiendo así, aplicaciones mucho más ricas y que cada vez se asemejan más a las aplicaciones de escritorio nativas.
Sin embargo, AJAX tiene un inconveniente a día de hoy: el desarrollo resulta todavía demasiado complejo. Esto es debido fundamentalmente a:
- 1. AJAX nos obliga a bajar un peldaño en el nivel de programación y a actualizar mediante JavaScript el árbol DOM del documento HTML.
- 2. La invocación asíncrona de peticiones al servidor resulta compleja y ocasiona problemas para manejar peticiones concurrentes.
- 3. No existen herramientas de desarrollo potentes para programar/depurar JavaScript.
- 4. Problema de compatibilidad de código entre distintos navegadores.
Estas limitaciones son conocidas entre la comunidad de desarrolladores y rápidamente han surgido múltiples frameworks para minimizar estos problemas sin renunciar a la riqueza que nos ofrece AJAX.
Uno de los últimos frameworks en aparecer en escena es el que ha desarrollado Google denominado GWT (Google Web Toolkit). Este framework resuelve de una manera muy especial los problemas antes descritos, como vamos a ver a lo largo de este artículo.
Modelo de programación
Lo que más impacta del modelo de programación que propone GWT
es que se elimina la programación en JavaScript. Eso no significa que
lo que se vaya a ejecutar en el navegador no vaya a ser JavaScript, pero a la
hora de programar no será necesario recurrir a este lenguaje. Tampoco
es necesario usar HTML para diseñar la interfaz de usuario. GWT cambia
drásticamente la forma en que hasta ahora se han desarrollado las aplicaciones
web y propone que toda la aplicación (tanto cliente, como servidor) se
programe usando un solo lenguaje, Java. Esto no significa que GWT proponga Applets
para las interfaces de usuario. Este nuevo framework se encarga automáticamente
de "compilar" la parte cliente de la aplicación a DHTML y JavaScript.
De esta manera, podemos usar las potentes herramientas de desarrollo del mundo
Java (Eclipse, NetBeans, IntelliJ, etc.) y eliminar uno de los problemas que
habíamos planteado al principio. El compilador GWT es capaz de compilar
(por el momento) sólo un subconjunto del API disponible en Java aunque
suele ser más que suficiente para el tipo de aplicaciones que vamos a
desarrollar. Para las clases cliente, estaremos limitados a usar los paquetes
"java.lang.*" y "java.util.*" además de los propios de GWT (com.google.gwt.user.client.*).
Otra característica interesante es que GWT ofrece dos modos distintos
de ejecución. El denominado "modo web" o "web mode" consiste en compilar
el código con el compilador GWT y ejecutarlo en un navegador habitual.
Este modo es el que se usará cuando la aplicación se ejecute en
producción pero seguimos sin poder depurar fácilmente la aplicación
durante su desarrollo. Para ello GWT propone un segundo modo de ejecución
denominado "hosted mode", que consiste en ejecutar la aplicación dentro
de una JVM. En este caso el código Java no es compilado a JavaScript
y por tanto podemos ejecutar la aplicación paso a paso, tal y como depuraríamos
cualquier aplicación en nuestro IDE favorito. Para utilizar este modo
de ejecución, dentro del jar de GWT (gwt-user.jar) se incluye una aplicación
(llamada shell o consola) que es capaz de ejecutar tanto el código cliente
(en un navegador propio) como el código servidor (en un pequeño
motor de servlets basado en Tomcat). Para quien haya programado interfaces de
usuario en Java, ya sea mediante AWT, Swing o SWT, programar con GWT le va a
resultar muy fácil. GWT incluye una amplia colección de controles
(widgets) que incluye desde los más básicos (Labels, Button, TextBox,
etc) hasta algunos más complejos y no disponibles en HTML de forma directa
(MenuBar, Tree o StackPanel). El modelo de eventos es similar al de Swing o
SWT, es decir, los controles contienen una lista de listeners a los que notificarán
los cambios producidos en su estado. En el listado 1 podemos ver un ejemplo
muy básico, aunque más adelante tendremos oportunidad de profundizar
en controles más sofisticados.
LISTADO 1 Listener sobre el evento clic
Button button = new Button("Click me");
final Label label = new Label();
button.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
label.setText("Hello World!");
}
});
En este ejemplo, hemos añadido un listener sobre el evento clic de un botón, que simplemente cambia el texto de una etiqueta. En GWT diferenciaremos entre controles (widgets) y contenedores (elementos que contienen widgets y gestionan el posicionamiento de los elementos hijos). Mediante estos contenedores (o paneles) se resuelve uno de los mayores problemas existentes al diseñar páginas HTML, el del posicionamiento. En GWT contamos con varios tipos de paneles (HorizontalPanel, VerticalPanel, DeckPanel, TabPanel, ScrollPanel, StackPanel, etc.).
Interactuando con el servidor
Hasta la aparición de AJAX, cada vez que una aplicación web tenía que realizar alguna operación en el servidor, se tenía que enviar una petición (GET o POST) a este, que tras efectuar la operación devolvía un documento HTML completo, que sustituía al anterior. Este modelo es muy ineficiente ya que en muchos casos, el tráfico de red que se genera es mucho mayor del que realmente es necesario. Imaginemos una tabla de resultados, donde el usuario decide eliminar una fila. En este caso después de que el servidor haya eliminado la fila solicitada, se tiene que devolver de nuevo la tabla resultante. Afortunadamente AJAX hace que este tipo de operaciones ya no tenga que seguir este modelo. Con AJAX podemos hacer peticiones al servidor sin sustituir el documento actual y además, podemos optimizar la respuesta haciendo que el servidor devuelva únicamente datos (sin incluir formato). Todo esto además de forma asíncrona por lo que se mejora mucho la experiencia de usuario.
GWT dispone de un mecanismo por el cual realizar este tipo de peticiones resulta muy sencillo. Este mecanismo ya existía en las antiguas arquitecturas cliente/servidor y se llama RPC (Remote Procedure Call). GWT lo ha rescatado para el entorno web como veremos más adelante en el segundo ejemplo.
Primera aplicación GWT
Antes de abordar un primer ejemplo debemos entender la estructura de directorio que exige GWT para nuestro proyecto.
Estructura de un proyecto GWT
Un proyecto GWT, pese a contener aplicaciones web, tiene una estructura más similar a un proyecto Java que a un proyecto J2EE (estructura WAR). GWT necesita ciertos ficheros adicionales y el seguimiento de una cierta nomenclatura en los paquetes Java. Nuestro primer ejemplo lo vamos a llamar "Ejemplo1" y utilizaremos el nombre de paquete "com.sp.gwt". La tabla 1 muestra la estructura que debería tener nuestro proyecto.
TABLA 1. Estructura de proyecto
- src/
- src/com/sp/gwt/Ejemplo1.gwt.xml
- src/com/sp/gwt/client/
- src/com/sp/gwt/client/Ejemplo1.java
- src/com/sp/gwt/server/ (opcional)
- src/com/sp/gwt/public/
- src/com/sp/gwt/public/Ejemplo1.html
- bin/
- www/
La carpeta "src" contiene:
- Los fuentes Java para el lado cliente que serán compilados a JavaScript.
Carpeta "src/com/sp/gwt/client/". - Los fuentes de los servicios RCP del servidor (que se compilarán a
.class). Carpeta "src/com/sp/gwt/server/". - Los recursos estáticos que necesite nuestra aplicación (imágenes,
css, etc.). Carpeta "src/com/sp/gwt/public/". - El descriptor de módulo (src/com/sp/gwt/Ejemplo1.gwt.xml). GWT basa
sus aplicaciones en módulos, y cada módulo debe tener su fichero
de configuración. El concepto es similar al de descriptor de despliegue
en J2EE. En este fichero se declaran los servicios RPC que van a ser invocados
desde el cliente así como los punto de entrada a nuestra aplicación
(entrypoints). - Página contenedora (src/com/sp/gwt/public/Ejemplo1.html). Aunque
hemos dicho que con GWT no es necesario escribir código HTML, existe
una pequeña excepción a esta regla. Debe existir al menos una
página HTML que contenga nuestra aplicación. Podemos por ejemplo
crear un diseño HTML que muestre una serie de banners publicitarios
con un espacio central que será donde se ubique nuestra aplicación.
Esta es la página que deberá invocarse desde un navegador para
lanzar la aplicación GWT.
La carpeta "bin" contiene el resultado de compilar las clases Java mediante el compilador javac (es decir, ficheros .class). Hay que notar que aquí se compilarán todas las clases Java, incluso las del lado cliente. Esto es así, ya que cuando ejecutemos la aplicación en el "hosted Mode" se ejecutarán estas clases y no el resultado de la compilación a JavaScript.
Por último, la carpeta "www" contiene nuestra aplicación lista para desplegar en un servidor web. Es decir, aquí se encuentran las clases cliente, compiladas a JavaScript mediante el compilador GWT. El compilador también se encarga de copiar aquí el contenido de la carpeta src/�/public que antes hemos mencionado. Para probar la aplicación basta con abrir el fichero www/�/Ejemplo1.html en un navegador y estaremos ejecutando nuestra aplicación en el modo web (web mode). El contenido de esta carpeta se puede desplegar en un simple servidor web ya que todo es HTML, JavaScript y XML.
Ya hemos visto la estructura que debe tener un proyecto GWT. El lector pensará que ahora es el momento de ponerse a crear todas las carpetas y ficheros que hemos comentado previamente. La respuesta es no. GWT incluye una utilidad (applicationCreator.cmd) que lo va a hacer por nosotros. Para nuestra aplicación "Ejemplo1", en primer lugar tenemos que crear el directorio base de nuestro proyecto, por ejemplo "c:\SP\Ejemplo1". A continuación invocaremos a la utilidad applicationCreator indicando el nombre de la clase de entrada a nuestra aplicación (EntryPoint), por ejemplo "com.sp.gwt.client.Ejemplo1":
C:\GWT\Ejemplo1>%GWT_HOME%\applicationCreator com.sp.gwt.client.Ejemplo1
La variable GWT_HOME deberá apuntar al directorio donde hayamos descomprimido previamente el fichero "gwt-windows-1.0.21.zip" (por ejemplo "c:\GWT\gwt-windows-1.0.21\").
Además de la estructura de proyecto anteriormente mencionada, se crearán dos scripts:
- Ejemplo1-compile.cmd. Este script será necesario invocar al compilador GWT y generar el código cliente en la carpeta "www".
- Ejemplo1-shell.cmd. Script que nos permitirá ejecutar la aplicación en "hosted mode" mientras estemos desarrollando la aplicación.
El fichero generado "src\com\sp\gwt\client\Ejemplo1.java", ya contiene código para un simple HelloWorld, por lo que ya podemos lanzar estos dos scripts y ver sus resultados.
C:\GWT\Ejemplo1>Ejemplo1-compile.cmd
El script de compilación creará la carpeta "www" con nuestra aplicación cliente ya compilada. En este punto podemos abrir con nuestro navegador la página "www\com.sp.gwt.Ejemplo1\Ejemplo1.html" y veremos la aplicación en ejecución en modo web.
Si por el contrario lanzamos el script para la ejecución en "hosted mode":
C:\GWT\Ejemplo1>Ejemplo1-shell.cmd
Se lanzará la consola de GWT y un navegador que automáticamente apuntará a la página contenedora "Ejemplo1.html", tal como muestra la figura 1. Echemos un vistazo al código Java de la aplicación que acabamos de generar (listado 2).
Figura 1. Consola GWT con Ejemplo1
LISTADO 2 Clase Ejemplo1
public class Ejemplo1 implements EntryPoint {
/**
* This is the entry point method.
*/
public void onModuleLoad() {
final Button button = new Button("Click me");
final Label label = new Label();
button.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
if (label.getText().equals(""))
label.setText("Hello World!");
else
label.setText("");
}
});
RootPanel.get("slot1").add(button);
RootPanel.get("slot2").add(label);
}
}
Lo primero que nos llama la atención es que la clase implementa la interfaz "com.google.gwt.core.client.EntryPoint". Esta interfaz obliga a implementar el método "onModuleLoad()" que se ejecuta cuando una página HTML declara el uso de un módulo GWT. En el ejemplo vemos que este método crea dos controles, un botón con el texto "Click me" y una etiqueta por el momento vacía. A continuación se añade un listener al botón que atiende al evento clic. Este listener (clase anónima que implementa la interfaz "ClickListener") se encarga de cambiar el texto de la etiqueta "label" cada vez que se haga clic sobre el botón.
Por último, sólo queda ubicar estos dos controles dentro de la página "contenedora". Para ello la clase "RootPanel" nos permite acceder a los elementos HTML que hayamos definido en la página "Ejemplo1.html". Esta página define una tabla con dos celdas a las que llama "slot1" (para el botón) y "slot2" (para la etiqueta):
<table align=center>
<tr>
<td id="slot1"></td>
<td id="slot2"></td>
</tr>
</table>
Además de proporcionar los "contenedores" donde podremos ubicar los controles que necesite nuestra aplicación, la página "Ejemplo1.html" debe declarar el uso del módulo que queramos usar. Eso se hace dentro de la cabecera () mediante la etiqueta "".
<meta name='gwt:module' content='com.sp.gwt.Ejemplo1'>
También es necesario incluir la referencia al fichero JavaScript "gwt.js" que se encarga de ejecutar el código del módulo(s) que declare la página. Este fichero JS ha sido creado por la utilidad applicationCreator y no es necesario que modifiquemos su contenido:
<script language="javascript" src="gwt.js"></script>
Por lo demás, somos libres de añadir el código HTML que deseemos. Normalmente se suele añadir una referencia a una hoja de estilos para personalizar la apariencia de los controles gráficos de GWT, pero esto lo veremos más adelante.
Interactuando con un servidor
Hemos visto un primer ejemplo de aplicación GWT en el que toda la lógica se ejecutaba en el lado cliente. Esto no es lo habitual en una aplicación web ya que normalmente siempre hay que manejar datos o ejecutar transacciones en un servidor centralizado. Para este tipo de operaciones GWT ya hemos comentado que GWT cuenta con el mecanismo RPC (Remote Procedure Call). Según la nomenclatura GWT, a las operaciones en el servidor se las denomina "servicios" aunque este concepto no debe confundirse con los servicios de las arquitecturas SOA típicamente implementados con tecnología Web Services.
Desde el punto de vista técnico, un servicio GWT es simplemente un Servlet que recibe una petición HTTP con una serie de parámetros y devuelve un resultado. No es un Servlet tradicional en el sentido en que tanto los parámetros que recibe como el resultado son objetos Java serializados. Otra diferencia importante es que el Servlet no devuelve una página HTML, sino que la respuesta es un documento XML con únicamente el objeto de respuesta serializado.
Ejemplo2
La receta para implementar un servicio GWT consiste siempre en crear dos interfaces y una clase (ver imagen 2).
Figura 2. Diagramas de clases para un servicio GWT.
En el segundo ejemplo vamos a implementar un sencillo servicio de búsqueda de empleados que, dado un número de departamento, nos devolverá un array con los empleados que pertenezcan a dicho departamento.
El primer paso es poner un nombre al servicio, en nuestro caso, "SrvBusqueda". Este nombre coincidirá con el nombre de la primera interfaz necesaria, que extenderá "com.google.gwt.user.client.rpc.RemoteService" y que declarará el método que realizará la operación de búsqueda. Esta interfaz debe declararse en el paquete con el código cliente.
package com.sp.gwt.client;
public interface SrvBusqueda extends RemoteService {
public Empleado[] buscarEmpleados(int nDepto);
}
Hay que tener en cuenta que tanto los parámetros de entrada como el resultado de los métodos que declaremos, van a ser serializados automáticamente por GWT cuando se realice la llamada desde el lado cliente. Por este motivo GWT impone que los tipos usados sean:
- 1. Tipos primitivos Java (int, float, char, etc.) así como sus respectivas clases wrapper (Integer, Float, Character, etc.).
- 2. Clases "String" o "java.util.Date".
- 3. Clases que implementen la interfaz "IsSerializable" y cuyos campos "no transient" sean a su vez serializables.
- 4. Array de tipos que cumplan las anteriores condiciones.
En nuestro ejemplo nos hemos creado un bean "Empleado" que implementa la interfaz "IsSerializable" y cuyos campos son de tipo "String" e "int", por lo que cumple la regla 3.
Una vez definida la interfaz principal, podemos crear la clase del lado servidor que implemente dicha interfaz. Esta clase debe llamarse igual que la interfaz más el sufijo "Impl" y debe extender la clase "RemoteServiceServlet". Como ya se comentó anteriormente esta clase va a correr como un Servlet dentro de un contenedor J2EE. El hecho de heredar de "RemoteServiceServlet", hace que nos ahorremos el trabajo de implementar los típicos métodos de un Servlet (doGet(), doPost(), etc.) y es esta clase la que se encarga de deserializar los parámetros recibidos y serializar el resultado de la operación en la respuesta. Por lo tanto, nuestra clase sólo se debe encargar de implementar el método buscarEmpleado() ya que del resto ya se encarga GWT.
LISTADO 3 Obteniendo la lista de empleados
package com.sp.gwt.server;
public class SrvBusquedaImpl extends RemoteServiceServlet implements SrvBusqueda {
... ...
public Empleado[] buscarEmpleados(int nDepto) {
Empleado[] lista=(Empleado[])map.get(new Integer(nDepto));
if (null==lista) {
return new Empleado[]{};
}
return lista;
}
}
El código del listado 3 se encarga de obtener la lista de empleados del departamento que se recibe como argumento. En un caso real, esta lista se debería obtener a partir de una base de datos, pero para simplificar el ejemplo, los datos se encuentran en una HashMap inicializada con unos datos de ejemplo.
Por último, debemos crear una última interfaz en el lado cliente. Esta última interfaz se va a utilizar para realizar la invocación de forma asíncrona desde el código cliente. El siguiente código muestra cómo seria esta interfaz para el ejemplo que estamos tratando:
package com.sp.gwt.client;
public interface SrvBusquedaAsync {
public void buscarEmpleados(int nDepto, AsyncCallback callback) ;
}
El nombre de esta interfaz debe ser idéntico al nombre de la interfaz principal del servicio, añadiendo el sufijo "Async". Si observamos la firma del método "buscarEmpleados" observaremos que esta no es idéntica a la del método original declarado en "SrvBusqueda". Esto se debe a que esta es la versión asíncrona del servicio, mientras que la primera interfaz define la versión "síncrona" que se utiliza únicamente en el lado servidor. Aun así la firma de la versión cliente o asíncrona, debe basarse en la firma inicial del método, realizando una pequeña transformación:
- 1. El nombre del método debe ser el mismo.
- 2. El tipo de retorno debe ser "void".
- 3. Los parámetros del método deben ser los mismos y en el mismo orden, a los que hay que añadir al final un parámetro de tipo "AsyncCallback". Este objeto, como veremos más adelante, se encargará de recibir la respuesta de la invocación al servidor. En el ejemplo de búsqueda que estamos implementando, este objeto se encargará de rellenar una tabla con los datos del array de empleados devuelto por el servicio.
Invocando al servicio
Ya hemos visto cómo crear el servicio y ahora sólo nos queda ver cómo invocarlo desde el lado cliente. Cuando el usuario hace clic sobre el botón de búsqueda, se invoca el código que muestra el listado 4.
LISTADO 4 Invocando al servicio
40 int nDepto = -1;
41 try {
42 nDepto = Integer.parseInt(txtDept.getText());
43 } catch (NumberFormatException e) {
44 mostrarError("Debe indicar un valor númerico como departamento");
45 txtDept.setText("");
46 return;
47 }
48
49 SrvBusquedaAsync srvBusqueda = (SrvBusquedaAsync) GWT
50 .create(SrvBusqueda.class);
51
52 ServiceDefTarget endpoint = (ServiceDefTarget) srvBusqueda;
53 endpoint.setServiceEntryPoint("/Ejemplo2/srvBusqueda");
54
55 AsyncCallback callback = new AsyncCallback() {
56 public void onSuccess(Object result) {
57 Empleado[] lista = (Empleado[]) result;
58 // eliminamos tabla con resultados anteriores
59 if (tabla != null) {
60 panel.remove(tabla);
61 }
62 tabla = new Grid(lista.length + 1, 3);
63 tabla.setWidth("450px");
64 tabla.setCellSpacing(0);
65 tabla.setCellPadding(2);
66 tabla.setStyleName("tabla");
67 // cabecera
68 tabla.setWidget(0, 0, new Label("Nombre"));
69 tabla.setWidget(0, 1, new Label("Apellido"));
70 tabla.setWidget(0, 2, new Label("Antiguedad"));
71 tabla.getRowFormatter().setStyleName(0,
72 "tabla-filaCabecera");
73 // filas
74 for (int i = 0; i < lista.length; i++) {
75 Empleado emp = lista[i];
76 tabla.setWidget(i + 1, 0,
77 new Label(emp.getNombre()));
78 tabla.setWidget(i + 1, 1, new Label(emp
79 .getApellido()));
80 tabla.setWidget(i + 1, 2, new Label(""
81 + emp.getAntiguedad()));
82 }
83 // añadimos tabla
84 panel.add(tabla);
85 }
86
87 public void onFailure(Throwable t) {
88 panel.add(new Label("Error: " + t.getMessage()));
89 }
90 };
91 srvBusqueda.buscarEmpleados(nDepto, callback);
En primer lugar se comprueba que el valor introducido por el usuario, sea numérico. La invocación al servicio comienza en la línea 49 de la clase "Ejemplo2". El primer paso consiste en obtener una instancia de la interfaz asíncrona del servicio que hemos creado previamente. A continuación (líneas 52-53) debemos indicar la URI con la que se haya desplegado el Servlet que implemente el servicio en cuestión. Para ello debemos usar la clase "ServiceDefTarget" y el método "setServiceEntryPoint()".
El tercer paso consiste en crear el objeto que gestionará el resultado de la invocación asíncrona. Para ello creamos una clase anónima que implemente la interfaz "AsyncCallback". Esta interfaz obliga a implementar dos métodos:
- * onSuccess() que se invocará en caso de que la ejecución haya sido correcta. Este método recibe como argumento el objeto devuelto por el servicio (en nuestro ejemplo, un array de empleados).
- * onFailure() se ejecutará cuando se produzca un error en el servicio.
En el método "onSucess()", el ejemplo se encarga de pintar una tabla con los datos de los empleados recibidos. Para ello utilizamos el control "Grid", que resulta muy fácil e intuitivo de usar. Este control nos permite crear una tabla de N columnas y M filas y añadir tanto código HTML como nuevos controles (widgets) en cada una de sus celdas. Recomendamos al lector consultar la documentación de este control para obtener más detalles.
El cuarto y último paso (línea 91), consiste en realizar la invocación pasando los argumentos necesarios y el objeto Callback que acabamos de crear. Hay que tener en cuenta que este método simplemente lanza la petición al servidor pero no espera la respuesta de este (recordemos que es una operación asíncrona, que internamente se lanza usando AJAX). De este modo no bloquearemos la pantalla al usuario durante la ejecución del proceso completo, que puede ser de unos cuantos segundos.
Desplegando en Tomcat
Ya tenemos nuestro segundo ejemplo listo para probar. Recordemos que para ejecutar una aplicación GWT tenemos dos opciones: "hosted mode" y "web mode".
Para probar un servicio en "hosted mode", podemos hacer que la propia shell de pruebas pueda ejecutar nuestro Servlet. Para ello simplemente tenemos que declarar el Servlet en el fichero descriptor del módulo (Ejemplo2.gwt.xml) como se indica a continuación.
<servlet path="/Ejemplo2/srvBusqueda" class="com.sp.gwt.server.SrvBusquedaImpl"/>
De esta forma tan sencilla, podemos probar nuestra aplicacion antes de desplegarla en un servidor.
Para probar la aplicación en modo web, debemos crear un fichero WAR. Este fichero debe incluir:
- 1. Clases compiladas del paquete "com.sp.gwt.server" así como todas sus dependencias. Para evitar problemas en este sentido, incluiremos todas los .class de la aplicación, aunque sepamos de antemano que clases como "com.sp.gwt.client.Ejemplo2" no se van a ejecutar en la JVM del servidor.
- 2. Los recursos estáticos que usa la aplicación (imágenes, css, js, etc) así como el resultado de la compilación GWT de las clases cliente. En definitiva, todo el contenido de la carpeta "www".
- 3. Librería "gwt-user.jar" dentro de la carpeta "WEB-INF/Lib". Hay que tener en cuenta que si se despliega la aplicación en Tomcat, hay que tener cuidado al incluir la librería "gwt-user.jar" ya que esta contiene las clases de los paquetes "javax.servlet.*", cosa que no está permitida por Tomcat. Por este motivo, hemos incluido en el CD una versión de este jar sin estas clases, al que hemos llamado "gwt-user-runtime.jar".
- 4. Descriptor de despliegue "web.xml" donde daremos de alta el Servlet "/srvBusqueda".
APUNTE
La utilidad projectCreator también permite crear la información necesaria para importar la aplicación en Eclipse. Para ello basta con ejecutar:
projectCreator �eclipse <nombre de proyecto>
Esto creará los ficheros .project, .classpath y .launch para poder desarrollar nuestro código bajo este popular IDE.
Para realizar este trabajo, lo más habitual es usar la herramienta Ant como hacemos en cualquier otro proyecto Java. GWT incluye una herramienta para generar un script Ant para nuestro proyecto. Esta utilidad se llama projectCreator.cmd:
C:\GWT\Ejemplo2>%GWT_HOME%\projectCreator �ant Ejemplo2
El fichero generado "Ejemplo2.ant.xml" no incluye un target para generar el WAR, por lo que es necesario añadir dos nuevos targets. Uno para invocar al compilador GWT (recordemos, script "Ejemplo2-compile.cmd") y otro para generar el WAR.
<target name="compilewww" description="Compila el código cliente">
<exec executable="cmd.exe">
<arg line="/c Ejemplo2-compile.cmd" />
</exec>
</target>
<target name="war" description="Genera el war con el código servidor" depends="compile, compilewww">
<war destfile="Ejemplo2.war" webxml="server-conf/web.xml">
<lib dir="lib-runtime">
<include name="**/*.jar" />
</lib>
<!-- Ubicacion de las clases ejecutables -->
<classes dir="bin" />
<!-- Recursos no-java que se incluiran en el fichero war -->
<fileset dir="www/com.sp.gwt.Ejemplo2">
<include name="**/*.*" />
</fileset>
</war>
</target>
Añadiendo este código al script Ant, ya estamos en disposición de generar el WAR mediante el comando:
C:\GWT\Ejemplo2>ant �f Ejemplo2.ant.xml war
El WAR generado (Ejemplo2.war) ya esta listo para ser desplegado en Tomcat como cualquier otra aplicación. Una vez arrancado el servidor, podemos abrir nuestro navegador preferido (recordemos que el código generado por GWT soporta cualquier browser) apuntando a la URL:
http://localhost:8080/Ejemplo2/Ejemplo2.html
Podemos ver el ejemplo 2 bajo el navegador Firefox en la imagen 3.
Figura 3. Ejemplo 2 en Firefox.
Aplicando estilos
GWT permite personalizar la apariencia de todos sus controles de una forma
muy sencilla. Para ello, se usan las ya conocidas y extendidas hojas de estilos
en cascada o CSS. Todos los controles GWT, cuentan con el método "setStyleName()"
que nos permite asociar un estilo con el control en cuestión. Todos los
controles tienen un estilo por defecto. El nombre del estilo inicial de cada
control, sigue el esquema:
gwt-<Nombre del control>
Por ejemplo, tenemos "gwt-TextBox", "gwt-Label", etc. Siempre podemos redefinir estos estilos por defecto y así, cambiar de forma global la apariencia de todos los controles de una aplicación sin necesidad de invocar siempre al método "setStyleName()".
Los estilos se deben incluir en un archivo CSS, que como el resto de recursos estáticos, debe situarse en la carpeta "src/com/sp/gwt/public". Además es necesario que la página contenedora (Ejemplo2.html) incluya la referencia a dicha página de estilos como se muestra a continuación:
<link rel="stylesheet" href="ejemplo2.css" type="text/css">
Características avanzadas
Además de todo lo que hemos contado hasta ahora, nos gustaría mencionar algunas de las características avanzadas que incluye el framework GWT:
- * Integración con JUnit. GWT dispone de TestCases especiales que permiten desarrollar fácilmente pruebas unitarias para las interfaces desarrolladas con GWT.
- * JSNI (JavaScript Native Interface). En casos especiales, puede ser necesario invocar código JavaScript directamente. GWT permite esta posibilidad, aunque no resulta sencilla. Es un equivalente a JNI (Java Native Interface) pero en el mundo web.
- * Gestión del historial del navegador. El hecho de la aplicación ya no se base en una sucesión de documentos, hace que perdamos la útil funcionalidad del botón "Volver" del navegador. GWT, nos permite almacenar un estado concreto de la aplicación en la historia del navegador, de modo que el usuario pueda volver a ese estado de manera directa mediante el botón "Volver".
Conclusiones
Google, con su framework GWT, ha reinventado la forma de programar aplicaciones web. Esta nueva "manera" resuelve muchos de los problemas que ofrece este entorno, que con la aparición de AJAX se han hecho aún más notables. La solución es buena para nosotros, los programadores, pues nos ahorra el tener que lidiar con la tecnología HTML/JavaScript, que originalmente no fue diseñada para el desarrollo de aplicaciones interactivas.
GWT está en beta (y conociendo a Google posiblemente lo estará durante algún tiempo) pero aun así resulta interesante por lo que tiene de innovación.
En cuento a los aspectos legales, GWT se distribuye con código fuente y con licencia Apache 2.0 (salvo el compilador y la consola de ejecución, de la cual sólo se distribuyen los binarios).
Para más información sobre el framework (documentación, tutoriales, foro, etc.) se recomienda visitar http://code.google.com/webtoolkit.
APUNTE
Desgraciadamente el compilador especial que incluye GWT no soporta caracteres especiales (vocales acentuadas, etc.) a menos que los ficheros fuente se encuentren codificados usando UTF-8. Se recomienda por tanto usar editores que soporten este tipo de codificación. Esta limitación es sólo para los ficheros compilados por el compilador GWT, es decir, los del lado cliente.
j2ee 
Reader Comments