I. Compilation▲
Il y a un peu plus de quatre ans, lorsque j'ai utilisé GWT pour la première fois, et malgré toutes les bonnes intentions du monde - en voulant utiliser Maven par exemple - tout était compliqué : la compilation, le packaging, le débogage, etc. Heureusement aujourd'hui, le plugin GWT pour Maven est arrivé à maturité et tout est beaucoup plus simple, comme nous allons le voir.
Commençons tout d'abord par les dépendances dont nous avons besoin :
<dependencies>
<dependency>
<groupId>
com.google.gwt</groupId>
<artifactId>
gwt-servlet</artifactId>
<version>
2.5</version>
<scope>
compile</scope>
</dependency>
<dependency>
<groupId>
com.google.gwt</groupId>
<artifactId>
gwt-user</artifactId>
<version>
2.5</version>
<scope>
provided</scope>
</dependency>
<dependencies>
gwt-user contient le code client de GWT, qui sera transformé en JavaScript après compilation. Par conséquent, il nous est utile uniquement pendant la phase de développement.
gwt-servlet contient le code serveur, dans le cas où vous souhaitez utiliser le framework RPC de GWT.
À présent, nous devons rajouter le plugin :
<build>
<plugins>
<plugin>
<groupId>
org.codehaus.mojo</groupId>
<artifactId>
gwt-maven-plugin</artifactId>
<version>
2.5.0</version>
</plugin>
</plugins>
</build>
Pour compiler rien de plus simple :
mvn clean install gwt:compile
Et pour lancer l'application (où gwttips.html est le fichier .html du module GWT) :
mvn gwt:run -DrunTarget=gwttips.html
Deux simplifications peuvent être effectuées.
La première consiste à rajouter le goal gwt:compile dans le cycle d'exécution de Maven et la seconde à ajouter le paramètre runTarget dans la partie configuration du plugin :
<plugin>
<groupId>
org.codehaus.mojo</groupId>
<artifactId>
gwt-maven-plugin</artifactId>
<version>
2.5.0</version>
<executions>
<execution>
<goals>
<goal>
compile</goal>
</goals>
</execution>
</executions>
<configuration>
<runTarget>
gwttips.html</runTarget>
</configuration>
</plugin>
À présent, la compilation et l'exécution s'effectuent de la manière suivante :
mvn clean install
mvn gwt:run
Vous avez sûrement constaté un warning pour chacune des commandes.
Le premier provient du plugin maven-war-plugin qui souhaite avoir le chemin au fichier web.xml du projet.
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-war-plugin</artifactId>
<version>
${maven-war-plugin.version}</version>
<configuration>
<webxml>
src/main/webapp/WEB-INF/web.xml</webxml>
</configuration>
</plugin>
Et le second provient du plugin gwt-maven-plugin :
<build>
<outputDirectory>
${project.build.directory}/${project.build.finalName}/WEB-INF/classes</outputDirectory>
</build>
Aucun de ces éléments n'est très important. Néanmoins éliminer tous les warnings le plus tôt possible dans un projet permet d'identifier de futurs problèmes potentiels plus rapidement.
De plus, concernant les plugins Maven, j'ai l'habitude de définir tous les plugins utilisés lors d'un build pour notamment contrôler leur version.
À l'instar d'un projet n'utilisant pas le plugin GWT, le Hot Swapping est possible dans une certaine limite. Il reste nécessaire d'arrêter le serveur sans recompiler le projet (même s'il arrive que le projet doive être recompilé dans certains cas), si vous souhaitez que les modifications sur les fichiers suivants soient prises en compte : *.gwt.xml, *.css, web.xml, etc.
D'autres goals sont tout autant intéressants :
- gwt:i18n génère les interfaces I18N pour les messages et les constantes ;
- gwt:generateAsync génère les interfaces *Async.java ;
- gwt:css génère les interfaces pour des fichiers css ;
- gwt:test lance les tests unitaires GWT.
Il est possible de rajouter ces goals dans le cycle de vie de Maven en les mettant dans le goal gwt:compile ou en les lançant manuellement. Ceci a l'avantage de vous laisser la main sur toutes les classes de votre projet et d'avoir la possibilité de supprimer le code inutile généré.
Pour plus de détails sur ces goals, je vous invite à consulter la documentation officielle du plugin.
Note sur le code : pour compiler et exécuter l'application :
mvn clean install
mvn gwt:run
II. Débogage▲
Pouvoir utiliser un débogueur est indispensable sur tout projet. GWT n'échappant pas à la règle, nous allons voir comment utiliser le débogueur avec Eclipse.
Nous allons commencer par créer une commande permettant de lancer le plugin GWT en mode debug.
- Name : nom de la commande.
- Location : chemin d'accès au script Maven mvn.bat (sous Windows) ou mvn (sous Unix).
- Working Directory : chemin d'accès au projet.
- Arguments : goal du plugin GWT que nous utiliserons (gwt:debug).
Pour finir, cliquez sur Apply -> Close/Run ou Run.
Nous devons ensuite créer une commande pour lier le débogueur d'Eclipse au plugin GWT.
- Name : nom de la commande (doit être unique)
- Project : projet à déboguer.
- Allow termination of remote VM : permet d'arrêter la Machine Virtuelle du débogueur d'Eclipse en même temps que celle du plugin. Ceci évite d'avoir à « killer » des processus à la main.
Pour finir, cliquez sur Apply -> Close/Debug ou Debug.
Il est important de noter que les deux commandes doivent être lancées dans un ordre précis. Tout d'abord, celle définie dans « External Tools », puis après que la ligne suivante est apparue dans la console :
[INFO] Listening for transport dt_socket at address: 8000
la commande définie dans « Debug ».
Si vous avez cliqué sur Apply, sans Run et Debug, il est nécessaire de retourner dans « External Tools Configurations… », de sélectionner la commande précédemment créée, puis de cliquer sur Run. Enfin, dans « Debug Configurations », sélectionnez la commande précédemment créée, puis cliquez sur Debug.
À noter que, pour les exécutions suivantes, vous trouverez les commandes directement dans les Favoris de External Tools et Debug.
Pour arrêter le mode Debug, en perspective JEE, il suffit de cliquer sur le bouton « Stop » de la console, et en perspective Debug, sur celui présent dans la barre d'outils.
III. Remote Logging▲
Logger des informations depuis la partie « server » d'un projet GWT se fait de la même manière que pour tout autre Framework, en utilisant votre bibliothèque de logging favorite. En revanche, pour la partie « client », les choses se corsent. Toute la partie cliente étant transformée en JavaScript, utiliser une bibliothèque standard telle que Log4j ou Logback n'est pas possible. Heureusement, Google a tout prévu et propose différentes solutions de logging, dont la majorité est - selon moi - complètement inutile, même pendant la phase de développement. Nous souhaitons avoir des logs dans un shell, dans une console d'un IDE, ou pour une application déployée en production, dans un fichier.
Il est donc nécessaire de faire des appels ajax consistant à envoyer des messages au serveur, qui seront ensuite traités pour être « journalisés » dans une console ou un fichier.
Le framework de logging de GWT émule java.util.logging, l'API de logging de la JDK. Par conséquent, dans la partie cliente, lorsque vous souhaitez récupérer un Logger, il est nécessaire d'utiliser la bonne classe :
import
java.util.logging.Logger;
public
class
MyClass {
final
static
Logger LOGGER =
Logger.getLogger
(
MyClass.class
.getName
(
));
}
Pour configurer le système de logging, il faut rajouter quelques lignes dans le fichier *.gwt.xml :
<inherits
name
=
"com.google.gwt.logging.Logging"
/>
<set-property
name
=
"gwt.logging.enabled"
value
=
"TRUE"
/>
<set-property
name
=
"gwt.logging.logLevel"
value
=
"FINEST"
/>
<set-property
name
=
"gwt.logging.simpleRemoteHandler"
value
=
"ENABLED"
/>
<set-property
name
=
"gwt.logging.consoleHandler"
value
=
"DISABLED"
/>
<set-property
name
=
"gwt.logging.developmentModeHandler"
value
=
"DISABLED"
/>
<set-property
name
=
"gwt.logging.popupHandler"
value
=
"DISABLED"
/>
<set-property
name
=
"gwt.logging.systemHandler"
value
=
"DISABLED"
/>
<set-property
name
=
"gwt.logging.firebugLogHandler"
value
=
"DISABLED"
/>
Tout d'abord, nous indiquons au compilateur de GWT que nous souhaitons utiliser le framework de logging :
<inherits
name
=
"com.google.gwt.logging.Logging"
/>
Ensuite, nous lui indiquons que nous souhaitons activer le système de logging :
<set-property
name
=
"gwt.logging.enabled"
value
=
"TRUE"
/>
« FALSE » désactive le système de logging. Toutes les références aux Loggers ne seront pas compilées en JavaScript.
Nous définissons aussi un seuil à partir duquel le logger sera actif.
<set-property
name
=
"gwt.logging.logLevel"
value
=
"FINEST"
/>
Les niveaux suivants sont possibles (du moins important au plus important) :
- FINEST ;
- FINER ;
- FINE ;
- CONFIG ;
- INFO ;
- WARNING ;
- SEVERE.
En utilisant le niveau FINEST, tout sera loggé.
Il est important de noter que si vous définissez un niveau SEVERE, tous les niveaux inférieurs seront ignorés lors de la phase de compilation en JavaScript.
Et pour finir, nous indiquons les handlers à activer ou à désactiver. Par défaut, en utilisant le framework de Logging (inherits), tous les handlers sont activés. Par conséquent, nous allons ajouter les cinq handlers et activer ceux qui nous intéressent. Ceci est beaucoup plus propre qu'ajouter uniquement les handlers que nous souhaitons désactiver.
Comme je l'ai indiqué dans l'introduction, la majorité sont inutiles puisqu'ils « log » côté client. Si vous souhaitez plus d'informations sur le sujet, je vous invite à consulter la documentation officielle sur le sujet.
Nous avons donc activé uniquement le handler qui enverra les messages au serveur :
<set-property
name
=
"gwt.logging.simpleRemoteHandler"
value
=
"ENABLED"
/>
Pour que le serveur puisse être appelé, il est nécessaire de définir la servlet qui sera en charge de réceptionner les messages, qui sont en réalité des java.util.logging.LogRecord.
<servlet>
<servlet-name>
remoteLogging</servlet-name>
<servlet-class>
org.isk.gwttips.server.servlet.LoggerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>
remoteLogging</servlet-name>
<url-pattern>
/gwttips/remote_logging</url-pattern>
</servlet-mapping>
L'objet LogRecord contient plusieurs informations, dont celles qui nous seront utiles sont :
- le nom du logger ;
- un message ;
- le niveau du log ;
- et potentiellement une exception.
public
class
LoggerServlet extends
RemoteServiceServlet implements
RemoteLoggingService {
@Override
public
String logOnServer
(
LogRecord record
) {
// Transférer les logs à une API de logging
return
null
;
}
}
Pour l'exemple, j'ai utilisé Log4j, mais évidemment ce n'est pas une obligation, et rien ne vous empêche de réutiliser l'API de logging standard.
Une possible implémentation de la méthode logOnServer() :
import
org.slf4j.Logger;
import
org.slf4j.LoggerFactory;
public
String logOnServer
(
LogRecord record
) {
final
Level level =
record
.getLevel
(
);
final
String message =
record
.getMessage
(
);
final
Throwable thrown =
record
.getThrown
(
);
final
Logger logger =
LoggerFactory.getLogger
(
record
.getLoggerName
(
));
if
(
Level.INFO.intValue
(
) ==
level.intValue
(
)) {
logger.info
(
message, thrown);
}
else
if
(
Level.WARNING.intValue
(
) ==
level.intValue
(
)
||
Level.CONFIG.intValue
(
) ==
level.intValue
(
)) {
logger.warn
(
message, thrown);
}
else
if
(
Level.SEVERE.intValue
(
) ==
level.intValue
(
)) {
logger.error
(
message, thrown);
}
else
{
// FINE, FINER and FINEST
logger.debug
(
message, thrown);
}
return
null
;
}
Il est important de garder à l'esprit que nous perdons toutes les informations sur le point d'origine du log, telles que le nom de la méthode dans laquelle la méthode de logging a été appelée, tout comme le numéro de la ligne :
[2012-12-15 18:04:29,062] - [btpool0-0] DEBUG | org.isk.gwttips.client.GwtTips#logOnServer() | LoggerServlet.java:37 - Module initializing...
L'explication est très simple : le code étant compilé en JavaScript puis minifié, il n'y a plus aucun lien entre le code Java d'origine et le code JavaScript généré.
Note sur le code : l'activation (ou désactivation) du framework de logging, le répertoire contenant les fichiers de log, ainsi que les différents niveaux de log, ont été déportés dans le fichier de configuration de Maven. L'exemple de la partie suivante s'appuiera sur cette solution pour l'utilisation de différents profils Maven.
<logs.path>
${basedir}</logs.path>
<log4j.level>
trace</log4j.level>
<gwt.log.enabled>
TRUE</gwt.log.enabled>
<gwt.log.level>
FINEST</gwt.log.level>
${basedir} étant le répertoire contenant le pom.xml.
J'ai aussi encadré les tokens entre des « @ » pour les distinguer des tokens provenant des fichiers properties, généralement définis de la manière suivante ${token}.
Pour compiler et exécuter l'application :
mvn clean install
mvn gwt:run
IV. Conclusion▲
Cet article a montré différentes astuces pour optimiser le développement avec GWT. Dans un second article, nous insisterons sur l'injection de dépendance et la sérialisation/désérialisation dans la partie cliente.
V. Remerciements▲
Cet article a été publié avec l'aimable autorisation de la société SoatSoat.
Nous tenons à remercier ClaudeLELOUP pour sa relecture orthographique attentive de cet article et Mickael Baron pour la mise au gabarit.