Vous n'êtes pas connecté. Connexion
|
|
Accueil > Intranet Michel Buffa > Cours web 2.0 année 2010-2011 > TP5 (long) : étude de la librairie GWT
TP5 (long) : étude de la librairie GWTDe $1Table des matières
IntroductionNous allons étudier la librairie GWT de Google utilisée par un nombre important de services Google tels que gmail ou google agenda. Cette tehnologie permet de développer des applications web faisant appel à des web services REST et des clients Ajax tout en développant exclusivement en Java. Le code serveur sera transformé en fichiers .class alors que le code client sera compilé en javascript. Comme GWT compile du java vers du javascript et ce pour les différents navigateurs du marché, la compilation peut être longue sur des ordinateurs peu puissants. Vous êtes prévenus ! En revanche le code compilé est très compact et bien optimisé. Les applications générées sont légères, etc. Supports de cours
Premiers pas avec GWT
Utiliser l'application GWT Showcase comme source de nombreux exemples de codePremière étape : exécuter l'application en ligne
Jouez avec, regardez les sources des démos en cliquant sur l'onglet "source" Seconde étape (à faire à la maison car longue) : compiler et exécuter l'application dans netbeansAttention, la compilation peut durer 15 minutes ! Il est cependant intéressant, même si vous ne le compilez pas, d'avoir le projet ouvert dans netbeans car il est truffé de bouts de code réutilisables. C'est une mine d'exemple dont on se sert et ressert régulièrement. Le projet est dans l'installation de GWT, dans le répertoire samples/showcase. La vidéo youtube qui montre comment installer, compiler et exécuter l'application de démonstration de GWT (GWT Showcase, aussi testable en ligne ici : http://gwt.google.com/samples/Showcase/Showcase.html) :
Attention, il faudra aussi modifier la ligne suivante dans le fichier gwt.properties (par defaut il y a gwt.version=2.1 or le plugin netbeans actuel ne reconnait que 2.0) : # GWT version: 1.5,1.6,1.7 or 2.0 gwt.version=2.0 IL FAUDRA FAIRE CETTE MODIF POUR QUASIMENT TOUS LES PROJETS DE CETTE ANNEE ! Pour info, voici le fichier gwt.properties que j'ai dans mon projet : # The names of the modules to compile (separated by a space character) gwt.module=com.google.gwt.sample.showcase.Showcase # Folder within the web app context path where the output # of the GWT module compilation will be stored. # This setting is only used for GWT 1.5. For newer versions please use # the rename-to attribute in the GWT module file (.gwt.xml). gwt.output.dir=/org.yournamehere.Main # Script output style: OBF[USCATED], PRETTY, or DETAILED gwt.compiler.output.style=OBF # Additional JVM arguments for the GWT compiler gwt.compiler.jvmargs=-Xmx256M # Specifies the number of local workers to use whe compiling permutations and module(s) gwt.compiler.local.workers=1 # The level of logging detail: ERROR, WARN, INFO, TRACE, DEBUG, gwt.compiler.logLevel=WARN # Script output style: OBF[USCATED], PRETTY, or DETAILED gwt.shell.output.style=OBF # The level of logging detail: ERROR, WARN, INFO, TRACE, DEBUG, gwt.shell.logLevel=WARN # Additional JVM arguments for the GWT shell/GWT hosted mode (GWT 1.6) # Add -d32 here and use at least GWT 1.7.1 to debug on a Mac # (32-bit JRE is required by GWT for debugging) gwt.shell.jvmargs=-Xmx256M # GWT version: 1.5,1.6,1.7 or 2.0 gwt.version=2.0 # GWT 2.0 only # Specifies the TCP port for the code server gwt.shell.code.server.port=9997 # GWT 2.0 only # Specifies the TCP port for the embedded web server gwt.shell.port=8888 # Additional GWT compiler arguments # GWT 2.0 compiler supports these: # -workDir The compiler's working directory for internal use (must be writeable; defaults to a system temp dir) # -gen Debugging: causes normally-transient generated types to be saved in the specified directory # -ea Debugging: causes the compiled output to check assert statements # -XdisableClassMetadata EXPERIMENTAL: Disables some java.lang.Class methods (e.g. getName()) # -XdisableCastChecking EXPERIMENTAL: Disables run-time checking of cast operations # -validateOnly Validate all source code, but do not compile # -draftCompile Enable faster, but less-optimized, compilations # -compileReport Create a compile report that tells the Story of Your Compile # -localWorkers The number of local workers to use when compiling permutations # -extra The directory into which extra files, not intended for deployment, will be written gwt.compiler.args= # Additional JVM arguments for JUnit tests gwt.test.jvmargs=-Xmx256M # Additional arguments for the GWT shell # e.g. -bindAddress 0.0.0.0 since GWT 2.0.1 gwt.shell.args= GWT et JPA, session et entity beansModification du fichier gwt.properties pour indiquer qu'on est en gwt 2.0 pas 2.1Le plugin gwt4nb n'est pas totalement à jour, il ne supporte que gwt 1.5, 1.6, 1.7 et 2.0, pas encore 2.1. Pour qu'il compile correctement le projet, modifiez la ligne suivante du fichier gwt.properties, dans "configuration files" # GWT version: 1.5,1.6,1.7 or 2.0 gwt.version=2.0 Ajout d'un entity bean Etudiant au projetComme nous avons fait dans les projets précédents, ajoutez une classe entity au projet, n'oubliez pas de créer une persistence unit en mode "create" sur la base de données jdbc/sample. Vous placerez cet entity bean dans un package "entities". Vous devriez obtenir ceci : Maintenant rajoutez deux "propriétés" nom et prénom à la classe Etudiant.java. Vous pouvez utiliser le wizard "insert code/add property", cela vous génèrera la déclaration des attributs mais aussi les get et set. Vous devez avoir le début de la classe Etudiant.java qui ressemble à ceci : @Entity public class Etudiant implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; private String nom; private String prenom; /** * Get the value of prenom * * @return the value of prenom */ public String getPrenom() { return prenom; }... Ajout d'un session bean GestionnaireEtudiants.javaRajoutez au projet un session bean stateless, sans interfaces (pour simplifier), que vous nommerez GestionnaireEtudiants.java. Si vous ne voyez pas "add session bean" dans le menu pour ajouter des éléments au projet, allez dans "other/java EE/Session bean", cf la capture d'écran ci-dessous : Dans ce session bean, vous ajouterez un entity manager et une méthode pour insérer un étudiant dans la base de données : @Stateless public class GestionnaireEtudiants { @PersistenceContext EntityManager em; public Etudiant ajouteStudent(String prenom, String nom) { Etudiant s = new Etudiant(); s.setPrenom(prenom); s.setNom(nom); em.persist(s); return s; } } Création d'un service RPC Gwt EtudiantServiceImpl.javaRemarque : 'est en réalité une servlet qui répond à la manière d'un web service, on l'invoque depuis du code javascript compilé à partir du code java/GWT que vous allez écrire... Utilisez le wizard de netbeans pour créer ce service :
On remarque que plusieurs classes ont été générées. Celles du package gwt.jpa1.client.* seront compilées en javascript et tourneront dans un navigateur web. Celles du package gwt.jpa1.server.* implémentent les services côté serveur (ils sont appelées par une GWT Servlet). On va remplacer le code du service côté serveur (la classe EtudiantServiceImpl.java), au lieu de : public class EtudiantServiceImpl extends RemoteServiceServlet implements EtudiantService { public String myMethod(String s) { // Do something interesting with 's' here on the server. return "Server says: " + s; } } On va appeler le session bean gestionnaire d'étudiants pour insérer un étudiant (vous pouvez utiliser le wizard "call entreprise bean") : public class EtudiantServiceImpl extends RemoteServiceServlet implements EtudiantService { @EJB GestionnaireEtudiants gestionnaireEtudiants; public String ajouteEtudiant(String prenom, String nom) { Etudiant e = gestionnaireEtudiants.ajouteEtudiant(prenom, nom); return "Un nouvel étudiant a été ajouté par le service GWT : #" + e.getId() + " - " + e.getPrenom() + " " + e.getNom(); } } Comme les services GWT implémentent une interface du service (pour permettre la génération de code d'interception, voir le cours !), il faut également modifier les deux interfaces : celles qui sert au code généré côté serveur (le fichier EtudiantService.java) et celle qui est implémenté par le code javascript côté client (le fichier EtudiantServiceAsync.java). Tant que l'on a pas modifié ces deux interfaces, une erreur figure dans la classe EtudiantServiceImpl.java indiquant qu'elle n'implémente pas les bonnes interfaces. Fichier EtudiantService.java (notez l'annotation de code qui correspond à l'URL de la servlet qui implémente ce service, on va retrouver cet URL dans la classe d'exemple d'utilisation du service, et dans le fichier web.xml dans la partie "Servlets") : @RemoteServiceRelativePath("servicesgwt/etudiantservice") public interface EtudiantService extends RemoteService { public String ajouteEtudiant(String prenom, String nom); } Fichier EtudiantServiceAsync.java : public interface EtudiantServiceAsync { public void ajouteEtudiant(String nom, String prenom, AsyncCallback<String> callback); } Noter que toutes les méthodes dans l'interface Asynchrone qui sera implémenté par le code compilé en javascript ont comme type de retour void ! C'est normal puisque la valeur de retour est fournie dans la fonction de callback ! Maintenant, nous allons modifier la classe d'exemple d'utilisation du service pour qu'elle fonctionne après que nous ayons modifié les interfaces d'utilisation. Prenez la peine d'étudier ce code ! Notez tout en bas dans la méthode getEtudiantService(), la présence de l'URL du service : public class EtudiantServiceUsageExample extends VerticalPanel { private Label labelReponseDuService = new Label(); private TextBox txtPrenom = new TextBox(); private TextBox txtNom = new TextBox(); private Button boutonEnvoyer = new Button("Envoyer au service"); public EtudiantServiceUsageExample() { add(new Label("Prénom et Nom: ")); add(txtPrenom); // modifie add(txtNom); // rajoute add(boutonEnvoyer); add(labelReponseDuService); // Create an asynchronous callback to handle the result. final AsyncCallback callback = new AsyncCallback() { public void onSuccess(Object result) { labelReponseDuService.setText((String)result); } public void onFailure(Throwable caught) { labelReponseDuService.setText("Problème de communication avec le service"); } }; // Listen for the button clicks boutonEnvoyer.addClickListener(new ClickListener(){ public void onClick(Widget w) { getEtudiantService().ajouteEtudiant(txtPrenom.getText(), txtNom.getText(), callback); } }); } /* aucune modification ici */ public static EtudiantServiceAsync getEtudiantService(){ // Create the client proxy. Note that although you are creating the // service interface proper, you cast the result to the asynchronous // version of // the interface. The cast is always safe because the generated proxy // implements the asynchronous interface automatically. EtudiantServiceAsync service = (EtudiantServiceAsync) GWT.create(EtudiantService.class); // Specify the URL at which our service implementation is running. // Note that the target URL must reside on the same domain and port from // which the host page was served. // ServiceDefTarget endpoint = (ServiceDefTarget) service; String moduleRelativeURL = GWT.getModuleBaseURL() + "servicesgwt/etudiantservice"; endpoint.setServiceEntryPoint(moduleRelativeURL); return service; } Enfin, nous allons modifier le point d'entrée de l'application, la classe MainEntryPoint.java, afin qu'elle ajoute dans la page html principale une instance de la classe d'exemple d'utilisation ci-dessus (qui est en réalité un élément de GUI, un VerticalPanel) : MainEntry.java : public void onModuleLoad() { RootPanel.get().add(new EtudiantServiceUsageExample()); } On exécute ensuite le projet et on doit obtenir ceci : Et on voit dans firebug l'appel au service et la manière dont GWT encode les paramètres d'envoi par HTTP POST. SI CELA NE MARCHE PAS ET INDIQUE QU'IL Y A UN PROBLEME DE COMMUNICATION AVEC LE SERVEUR :
/div[5]/div[5]/pre[7], line 2, column 1: EOF expected est la même que dans la ligne spécifiant l'URL du service dans le code client (avant, avant dernière ligne) : /* aucune modification ici */ public static EtudiantServiceAsync getEtudiantService(){ // Create the client proxy. Note that although you are creating the // service interface proper, you cast the result to the asynchronous // version of // the interface. The cast is always safe because the generated proxy // implements the asynchronous interface automatically. EtudiantServiceAsync service = (EtudiantServiceAsync) GWT.create(EtudiantService.class); // Specify the URL at which our service implementation is running. // Note that the target URL must reside on the same domain and port from // which the host page was served. // ServiceDefTarget endpoint = (ServiceDefTarget) service; String moduleRelativeURL = GWT.getModuleBaseURL() + "servicesgwt/etudiantservice"; endpoint.setServiceEntryPoint(moduleRelativeURL); return service; } et que dans le fichier web.xml vous avez aussi un URL cohérent pour la Servlet (3ème ligne) : <servlet-mapping> <servlet-name>EtudiantService</servlet-name> <url-pattern>/gwt.jpa1.Main/servicesgwt/etudiantservice</url-pattern> </servlet-mapping> Si ces trois références ne sont pas compatibles, alors la communication client/serveur ne pourra pas se faire. Vous pouvez regarder quel URL est appelé lorsque vous cliquez sur le bouton ajouter, à l'aide de firebug (touche F12 dans Firefox), n'oubliez pas d'activer le debug réseau et la console. Manipuler des instances d'entity beans dans la partie "cliente" de l'applicationLà le problème devient un peu plus délicat, nous allons le voir. En effet, la partie cliente de l'application, ne l'oublions pas, va être compilée en javascript. Il est donc nécessaire que le compilateur GWT ait accès aux sources ou aux jars des différentes classes utilisées par l'application. Nous allons voir comment nous y prendre... en commençant par ajouter une méthode dans le service GWT... Ajout d'une méthode getAllEtudiants() dans le service GWTpublic Collection<Etudiant> getAllEtudiants() { return gestionnaireEtudiants.getAllEtudiants(); } N'oublions pas de rajouter cette méthode dans les deux interfaces... EtudiantService.java (rappel : cette méthode sert à générer la servlet d'interception GWT) : @RemoteServiceRelativePath("servicesgwt/etudiantservice") public interface EtudiantService extends RemoteService { public String ajouteEtudiant(String prenom, String nom); public Collection<Etudiant> getAllEtudiants(); } EtudiantServiceAsync.java (et toujours void comme valeur de retour et un paramètre supplémentaire pour la fonction de callback). Rappel, cette interface sert au code généré en javascript. public interface EtudiantServiceAsync { public void ajouteEtudiant(String nom, String prenom, AsyncCallback<String> callbackAjoutEtudiant); public void getAllEtudiants(AsyncCallback<String> callback); }
Ajout d'un bouton pour lister tous les étudiants dans la GUI du client :Travail à faire : rajouter l'appel de cette méthode dans la classe cliente EtudiantServiceUsageExample.java. Vous ajouterez un bouton, un écouteur qui appelle dans sa méthode onclick() la méthode getAllEtudiants() du service distant, et vous écrirez aussi la fonction de callback qui modifie un label pour afficher cette liste. On doit avoir un résultat de ce type : Mais, ce n'est pas aussi simple !!!! Il y a des erreurs de compilation, voyons pourquoi !Et oui, normalement, vous devriez avoir des erreurs de compilation du genre : Ah ah ! Pour compiler en javascript la classe qui crée la GUI (la classe EtudiantServiceUsageExample.java), on a besoin de la classe Etudiant.java. Pourtant, le compilateur java la trouve non ? Netbeans ne sort pas d'erreurs, les lignes ne sont pas soulignées en rouge ! Oui, mais ici, le source est compilé par le compilateur GWT. Il faut lui indiquer où se trouvent les sources de cette classe. On précise cela dans le fichier du "module GWT", soit le fichier Main.gwt.xml qui se trouve dans le package principal de notre application (gwt.jpa1). Double cliquons sur ce fichier et rajoutons deux lignes pour qu'il trouve les sources : <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.7.0//EN" "http://google-web-toolkit.googlecode.com/svn/tags/1.7.0/distro-source/core/src/gwt-module.dtd"> <module> <!-- ce sont des chemins relatifs par rapport au package gwt.jpa1 --> <source path="entities" /> <source path="client" /> <inherits name="com.google.gwt.user.User"/> <!-- Inherit the default GWT style sheet. You can change --> <!-- the theme of your GWT application by uncommenting --> <!-- any one of the following lines. --> <!-- <inherits name='com.google.gwt.user.theme.standard.Standard'/> --> <!-- <inherits name="com.google.gwt.user.theme.chrome.Chrome"/> --> <!-- <inherits name="com.google.gwt.user.theme.dark.Dark"/> --> <entry-point class="gwt.jpa1.client.MainEntryPoint"/> <!-- Do not define servlets here, use web.xml --> </module> Ce sont les deux lignes commençant par <source path="...."/> qui nous intéressent. Nous indiquons ici qu'il faut aller voir dans le package "entities" et dans le package "client". Il s'agit en fait de sous-packages du package principal. Ces deux lignes rajoutent donc :
Etant donné que la classe Etudiant.java était dans le package "entities" et non "gwt.jpa1.entities", nous renommons le package dans netbeans : Voilà, si vous recompilez après avoir modifié Main.gwt.xml et le nom du package entities pour qu'il soit sous package de gwt.jpa1, les choses devraient progresser ! On a maintenant les erreurs suivantes : Rajouter le "source" des annotations de persistence pour compilation vers javascriptHmmm rappelez-vous que les annotations de code font partie intégrante du langage java, au même titre que les attributs, les méthodes, les constructeurs etc... Pour compiler correctement la classe Etudiant.java (en javascript), le compilateur GWT a besoin des sources des annotations. Ah... jusqu'en 2009, il fallait réellement les sources (que l'on pouvait trouver sur internet ou dans les repositories de source code de java EE), mais depuis cette année (depuis GWT 2.0), le compilateur sait aussi compiler des .class vers du javascript ! Qu'à cela ne tienne, nous allons simplement rajouter au projet le jar contenant la librairie de persistence de Java EE. Si la classe Etudiant avait contenu des annotations de code JAXB (transformation xml) ou Jersey (web services), il aurait fallu rajouter les classes correspondantes dans les librairies du projet (le compilateur GWT ajoute les librairies dans ses chemins de recherche de sources) Nous pouvons trouver toutes les librairies de java EE dans le répertoire d'installation de glassfish, çà tombe bien ! Adaptez le répertoire à votre machine : Copiez ce fichier javax.persistence, ajoutez-le dans un répertoire lib de votre projet, ajoutez le jar aux librairies du projet. Recompilez, cela devrait marcher. Affichage des résultats dans une FlexTable (un tableau de taille variable)
Voici une CSS minimale : .cw-FlexTable td { border: 1px solid #BBBBBB; padding: 3px; } Et la ligne de code qui associe le style à la FlexTable : // Pour le lien avec la feuille de style Student.css studentTable.addStyleName("cw-FlexTable"); A venir : comment modifier un projet Java EE Existant pour qu'il fonctionne avec GWT
Mots clés:
|
Powered by MindTouch Deki Open Source Edition v.8.08 |