TP5 (long) : étude de la librairie GWT

De $1

Version de 05:06, 19 Avr 2024

cette version.

Revenir à liste des archives.

Voir la version actuelle

Introduction

Nous 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

  • Copie locale de GWT 2.1.0 :
  • Pour cette étape, vous allez suivre ce tutorial (en anglais) de Sun : http://netbeans.org/kb/docs/web/quic...bapps-gwt.html, il vous apprendra par ailleurs quelques astuces avec netbeans :
    • Installer le plugin GWT,
    • Créer un projet web avec le support GWT,
    • Comprendre la structure d'un projet GWT,
    • Créer un service RPC qui va faire de l'ajax en coulisse,
    • Utiliser une feuille de style CSS pour spécifier le look & feel d'éléments d'interface utilisateur,
    • Utiliser l'éditeur de CSS interactif de netbeans (super outil !),
    • Afficher le javadoc en temps réel d'un projet Java, y compris la javadoc de GWT, juste en positionnant le curseur sur le code,
    •  Debugger une application en "Hosted Mode", mettre des points d'arrêt etc. Attention, par rapport au tutorial, avec GWT 2.1.0 cela requiert l'installation d'une extension firefox (vous verrez).

Utiliser l'application GWT Showcase comme source de nombreux exemples de code

Premiè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 netbeans

Attention, 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 ul 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

Bien sûr, vous remplacerez les chemins par ceux de votre installation de GWT 2.1.0 !

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 beans

Création d'un projet web utilisant GWT

Snap1.jpg

Snap3.jpg

Snap4.jpg

Snap8.jpg

Snap9.jpg

Modification du fichier gwt.properties pour indiquer qu'on est en gwt 2.0 pas 2.1

 Le 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 projet

Comme 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 :

Snap10.jpg

Snap11.jpg

Snap12.jpg

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.java

 Rajoutez 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 :

Snap13.jpg

Snap14.jpg

Snap15.jpg

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.java

Remarque : '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 :

 Snap16.jpg

Snap17.jpg

Snap18.jpg

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("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() + "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 :

Snap19.jpg

Et on voit dans firebug l'appel au service et la manière dont GWT encode les paramètres d'envoi par HTTP POST.

Snap21.jpg