GWT se ha convertido en uno de los frameworks más populares para el desarrollo de aplicaciones web que siguen el paradigma AJAX. En este artículo exploramos la creación de aplicaciones web modulares utilizando Apache Maven para la construcción de la solución y la gestión de sus dependencias.
¿Qué es GWT?
GWT es una potente herramienta Java creada por Google Inc. para el diseño de aplicaciones web cross-browser basadas en comunicaciones AJAX.
La base del funcionamiento de esta herramienta es el GWTCompiler, un compilador que traduce código Java a código JavaScript. De esta manera se permite a los desarrolladores implementar la parte cliente de su aplicación utilizando alguno de los entornos Java existentes en el mercado, como Eclipse o NetBeans, agilizando así el siempre costoso desarrollo en JavaScript. Por otra parte, el compilador de GWT permite aislar al desarrollador de los detalles y características propias de los navegadores web, haciendo posible un desarrollo multi-browser.
Adicionalmente, GWT facilita el proceso de pruebas y el TDD impactando positivamente en la calidad final del software desarrollado.
Creación de módulos en GWT
La creación de una aplicación de tamaño medio o grande exige un enfoque modular, por diversas razones como:
- Mejorar la depuración,
- facilitar el mantenimiento,
- incrementar la reutilización del código
- o permitir el trabajo en paralelo de cierto número de desarrolladores.
GWT no es ajeno a dicha necesidad de modularización y de hecho la permite, pero con algunas características especiales que resultan en cierto modo extrañas al programador habitual de Java.
En esencia, los módulos de GWT se encapsulan en paquetes JAR de Java con dos características especiales:
-
Deben incorporar el código fuente de las clases, y no solamente las clases compiladas, ya que el GWTCompiler necesita dichas fuentes.
-
Vienen definidos por un archivo con extensión .gwt.xml, que entre otras cosas, establece cuál será la referencia lógica del módulo.
En el siguiente punto exploraremos la construcción de una pequeña aplicación cliente-servidor, con comunicaciones RPC entre ambas y un diseño modular de la parte cliente, construida usando la librería.
Caso de uso
La solución que vamos a construir está inspirada en el código de ejemplo que provee GWT al crear un proyecto. Básicamente, consiste en dos paneles, uno con el logo de GWT y un botón, y otro oculto que aparece al pulsar el botón y que contiene un texto que devuelve un servlet desde la parte servidora de la aplicación. Cada uno de estos paneles estará contenido en un módulo distinto y serán importados por el módulo raíz de la interfaz.
Por otra parte, la parte servidora de la aplicación consistirá en un paquete tipo JAR que contendrá la implementación de un servicio muy simple que en tiempo de ejecución se comportará como un servlet. Este servicio tendrá un método que recibirá una cadena de texto y le añadirá otra cadena distinta, devolviendo el resultado. Nótese que esta parte no sigue necesariamente la estructura de los módulos de GWT, ya que no va a ser compilada para generar código JavaScript.
Finalmente tendremos otro paquete que contendrá las interfaces necesarias para generar el servicio.
Creación de un módulo GWT
Para crear esta solución nos apoyaremos en una herramienta de gestión y construcción de proyectos llamada Maven, de Apache Software Foundation, que nos permitirá gestionar la configuración de una manera sencilla. Para poder usar Maven, necesitamos crear una estructura de ficheros de esta forma:

Un proyecto construido con Maven debe cumplir estas tres características:
- La existencia de un archivo llamado pom.xml, que define el nombre final del proyecto, las dependencias que importa, los plug-in que se usan en su construcción, y otras muchas cosas.
- Si el proyecto incluye código java, deben estar contenidas en src/main/java o sus hijos, salvo que se especifique otra configuración.
- Todos los recursos que no sean código java deben estar contenidos en src/main/resources o sus hijos, salvo que se especifique otra configuración
Este proyecto en concreto cumple otra característica: tiene la estructura propia de un módulo GWT. Esta estructura viene determinada, principalmente, por la presencia de un archivo con extensión gwt.xml. El módulo se podrá referenciar por su nombre lógico, consistente en la concatenación de la ruta relativa al classpath del archivo mencionado y el nombre del mismo, eliminada la extensión. En este caso particular, el nombre lógico será:
com.tecsisa.art.gwt.modularization.components.Component2
Durante la compilación y salvo que se le especifique otra cosa, el GWTCompiler buscará las fuentes Java en la ruta del archivo del módulo concatenándole “.client”, así que, tal como se muestra en la imagen, los archivos .java del módulo deben estar contenidos en el paquete:
com.tecsisa.art.gwt.modularization.components.client
Finalmente, y para que el compilador pueda encontrar dichas fuentes, debemos indicar que se incluyan en el archivo JAR resultante de la construcción de ese paquete. Esto se consigue añadiendo el siguiente código a la etiqueta <build> del archivo pom.xml:
<resources> <resource> <directory>src/main/java</directory> </resource> <resource> <directory>src/main/resources</directory> </resource> </resources>
Incorporación de los módulos GWT a la raíz de la parte cliente
El módulo raíz de la parte cliente sigue una estructura muy similar a la de los módulos de componentes, con algunas características adicionales.
La primera de ellas es que su archivo gwt.xml debe contener las instrucciones de importación de otros módulos, referenciándolos por el nombre lógico:
<inherits name='com.tecsisa.art.gwt.modularization.components.Component1'/> <inherits name='com.tecsisa.art.gwt.modularization.components.Component2'/>
La segunda es que, al tratarse de un módulo raíz, la clase de la que van a colgar el resto de elementos debe implementar la interfaz EntryPoint, que obliga a definir un método llamado onModuleLoad(). Este método tiene cierta analogía con el método main() de ciertas aplicaciones Java, y sirve para instanciar los elementos de la interfaz y definir la jerarquía de los mismos. En este caso el EntryPoint será la clase GwtRoot, contenida, de acuerdo con la estructura indicada anteriormente, en el paquete: com.tecsisa.art.gwt.modularization.client
Para que el compilador sea consciente de la presencia de este EntryPoint, lo definiremos en el archivo gwt.xml de la siguiente manera:
<entry-point class='com.tecsisa.art.gwt.modularization.client.GwtRoot'/>
La tercera característica es que el módulo debe contener un archivo html que incorporará el código JavaScript generado por el GWTCompiler y que será, en definitiva, lo que se visualice con un navegador al acceder a la aplicación. Este fichero estará contenido en una carpeta “public” situada a la misma altura que el archivo gwt.xml del módulo.
Para poder realizar la compilación JavaScript automáticamente, usaremos un plug-in de Maven llamado gwt-maven-plugin. Este plug-in permite muchas opciones de configuración, pero de momento nos fijaremos en las dos más básicas:
- Definición del punto de entrada de la aplicación, es decir, del archivo html antes señalado. Esto lo conseguiremos con el siguiente código, donde lo que viene antes de la barra es el nombre lógico del módulo, y lo que viene después es el nombre del archivo html:
<configuration> <runTarget> com.tecsisa.art.gwt.modularization.GwtRoot/GwtRoot.html </runTarget> </configuration>
- Indicación del módulo a compilar:
<executions> <execution> <configuration> <module> com.tecsisa.art.gwt.modularization.GwtRoot </module> </configuration> <goals> <goal>compile</goal> </goals> </execution> </executions>
Creación de la parte servidora de la aplicación
La parte servidora de la aplicación contiene un servicio RPC muy simple que recibe una cadena de texto cualquiera, le concatena “: desde el servidor” y devuelve el resultado. Para configurar dicho servicio usaremos el módulo RPC de la librería GWT que nos facilitará bastante la tarea.
Para poder crear dicho servicio, son necesarias un mínimo de tres clases Java. Estas clases no serán compiladas por el GWTCompiler, así que sus rutas quedan a la elección del programador.
La primera de ellas será la interfaz del servicio. En nuestro caso se llamará SampleService y definirá el método del servicio, que llamaremos getLabel y recibirá un String como único parámetro. Para que GWT pueda crear el servicio correctamente, es necesario que dicha interfaz extienda la clase abstracta RemoteService y que se le especifique la ruta relativa del servicio mediante el uso de la anotación @RemoteServiceRelativePath.
La segunda clase será otra interfaz, que llamaremos SampleServiceAsync. Esta interfaz debe definir un método que se llamará igual que el método de servicio y que recibirá los mismos parámetros, junto con un objeto que implementa la interfaz AsyncCallback. Este objeto es quien se encargará de procesar la respuesta del servicio cuando esta llegue, ya que la invocación al mismo es asíncrona y retornará inmediatamente, sin esperar dicha respuesta. Por ello AsyncCallback es una interfaz que contiene dos métodos (onSuccess y onFailure) donde el programador especificará como quiere que se procese la respuesta.
La tercera clase consistirá en la implementación del servicio. Esta clase, que llamaremos SampleServiceImpl, debe implementar la primera interfaz que hemos definido y asignar una funcionalidad al método del servicio. Finalmente, haremos que la clase extienda de RemoteServiceServlet, que es la implementación de los servlet que ofrece GWT.
Para que la interfaz de la aplicación tenga acceso al servicio así definido, debemos indicarlo en el fichero gwt.xml del módulo raíz, con un código de este estilo:
<servlet path="/sampleService" class="com.tecsisa.art.gwt.modularization.server.SampleServiceImpl"/>
Donde el path es la ruta relativa que hemos establecido en la anotación de la primera interfaz, y la clase es la que contiene la implementación del servicio.
Construcción y despliegue de la solución
Usando Maven de acuerdo con lo explicado en los puntos anteriores, la construcción y el despliegue de la solución es realmente sencilla. Basta con descargarse el código fuente, situarse con una consola del sistema operativo en la carpeta raíz y ejecutar la instrucción
> mvn clean install
La primera vez que realicemos este paso, Maven tendrá que descargarse todas las librerías que necesita la solución, así que es posible que tarde unos minutos. Sin embargo, construcciones posteriores deberían ser mucho más rápidas, al no ser necesaria la descarga.
Hecho esto, debemos situarnos en la carpeta del proyecto art-gwt-modularization-root y ejecutar
> mvn jetty:run
Con esta instrucción haremos que se arranque un servidor de aplicaciones Jetty que hemos definido a los efectos en el pom del proyecto mediante un plug-in. Hecho esto, sólo nos quedará abrir un navegador y acceder a la siguiente URL:
http://localhost:8888/com.tecsisa.art.gwt.modularization.GwtRoot/GwtRoot.html
Ahí podremos ver la aplicación en funcionamiento. Pulsando sobre el botón que aparece en el centro de la pantalla, aparecerá un nuevo panel que, si todo ha funcionado correctamente, contendrá un texto que nos ha devuelto el servicio creado.
Por otra parte, el plug-in de GWT que hemos utilizado permite otras opciones de ejecución que facilitan el desarrollo. Mediante el uso del comando
> mvn gwt:run
haremos que se ejecute un navegador especial de GWT, que nos permite visualizar cambios en el código simplemente refrescando la ventana, sin necesidad de compilar la aplicación. Otra posible opción es el uso del comando
> mvn gwt:debug
que provoca la ejecución del mismo navegador que con el comando run, y que además permite conectarse al proceso de ejecución mediante una aplicación externa como el entorno de desarrollo que estemos utilizando, y debuguear la aplicación GWT, con puntos de ruptura, ejecución paso a paso y otros elementos usuales.
Conclusiones
A lo largo de este artículo hemos explorado las posibilidades que ofrece GWT para construir aplicaciones modulares. Resulta obvio que para construir una aplicación sencilla como la expuesta de ejemplo este enfoque resulta innecesariamente complicado, pero si nuestra aplicación va a alcanzar un tamaño medio o grande, la modularización resulta prácticamente imprescindible por las razones expuestas en puntos anteriores.
Por otra parte, el ejemplo expuesto ilustra otras posibilidades del framework, como la creación de un servicio RPC de una forma sencilla y estructurada. Las posibilidades que proporciona GWT al diseñador de aplicaciones web son de muy largo alcance, comenzando por el hecho de que su uso permite la creación de todas las capas de una aplicación usando el mismo lenguaje de programación y la obtención de un resultado que es independiente tanto del sistema operativo del usuario como del navegador que use.
La solución expuesta contiene únicamente dos capas: una parte cliente consistente en los módulos GWT creados, y otra parte servidora que define un servicio RPC sencillo. Esta estructura, sin embargo, puede resultar insuficiente para la creación de una aplicación de cierto tamaño. En soluciones más complejas puede ser necesario añadir a la arquitectura expuesta, las correspondientes capas de servicio y acceso a datos. En este sentido, GWT se presta a ser integrado con facilidad con otros conocidos frameworks multipropósito como Spring o incluso con plataformas SOA como ServiceMix.
Código fuente de la solución
El código fuente de la solución descrita se puede descargar siguiendo este enlace.
