Lorsqu'on développe en GWT, on dispose de la facilité de développement Java pour générer notre HTML et JavaScript. Cependant, il arrive parfois qu'on ait besoin d'écrire quelques bouts de code en JavaScript. Ceci pour diverses raisons : on ne trouve pas les bons composants dans la bibliothèque de GWT, on veut récupérer des bibliothèques JavaScript existantes, etc.. c'est là que le framework nous fournit une très bonne API nommé JSNI (pour JavaScript Native Interface). Elle n'est pas sans rappeler le mécanisme de son cousin JNI (Java Native Interface).
Cependant, et on le lit un peu partout, il n'est pas conseillé d'écrire de gros traitements en JSNI pour de très bonnes raisons :
Suite:
le compilateur GWT, qui se charge de transformer le code Java en JavaScript, va prendre le code JavaScript et l'insérer « tel quel » dans le résultat de la compilation. Vous me direz, « très bien, c'est ce que je veux ! ». Mais le problème est qu'on perd au passage tout l'intérêt du compilateur. Celui-ci fait de nombreuses optimisations de code, s'assure de la compatibilité multi-navigateur et génère les permutations spécifiques par navigateur pour bénéficier du code le plus performant .
Mon but ici, n'est pas de vous expliquer comment on se sert de JSNI ou de son mécanisme interne (et encore moins celui de JNI), mais plutôt de vous donner un exemple issu d'un retour d'expérience. Récemment sur un cas concret, j'étais bien content de m'appuyer sur JSNI et surtout la surpuissance du concept des overlays !
Le projet en consistait à créer un gadget OpenSocial pour Jira 4, le célèbre outil de bug-tracking. Le gadget manipulait des objets en Java définis en JavaScript. Le format en question ? Simplement du JSON !
Côté client, on obtient du code JSON comme celui-ci :
var personne = {nom:"Martin",prenom:"Paul", adresse:{rue:"chemin de Traverse",codePostal:"31500", ville:"Toulouse"} };
En JavaScript, je peux très facilement le manipuler de cette manière :
var nom = personne.nom ;
var rue = personne.adresse.rue ;
Mais qu'en est-il de Java dans mon application GWT ? Comment pouvoir obtenir des objets Java comme ceux ci-dessous à partir du JSON ?
public class Personne {
public String getNom() ;
public String getPrenom() ;
public Adresse getAdresses() ;
}
public class Adresse {
String getRue();
public int getCodePostal();
public String getVille() ;
}
C'est là qu'entre en scène JSNI et surtout JavaScriptObject (JSO) en version Overlay !
Sans entrer dans le détail de JSNI et de l'objet JSO (de nombreuses ressources en ligne expliquent tout cela très bien), JSNI est simplement une syntaxe qui me permet d'écrire des portions de code JavaScript au milieu de mon code Java (et inversement), généralement le corps d'une méthode. JSO est, quant à lui, un objet plutôt abstrait qui renvoie à cette méthode Java un objet représentant un élément JavaScript, que je pourrais ensuite utiliser en paramètre d'une autre méthode, également implémentée en JSNI.
La manipulation de cet objet est cependant très limitée : je ne peux pas créer d'instance d'un JSO dans mon code Java, il s'agit vraiment d'un objet encapsulant du JavaScript et qui permet de franchir la barrière des deux mondes.
La révolution avec les Overlays est de procurer, entre autre, une notion d'héritage entre JSO. On peut ainsi créer nos propres JSO avec une plus value, organiser nos objets de manière logique en intégrant de la hiérarchie ou de la composition.
En effet, si je reprends mon exemple précédent, je peux tout à fait écrire quelque chose comme :
public class Personne extends JavaScriptObject {
protected Personne(){}
public final native String getNom()/*-{ return this.nom; }-*/;
public final native String getPrenom()/*-{ return this.prenom; }-*/;
public final native Adresse getAdresses()/*-{ return this.adresse; }-*/;
}
Et bien évidemment la classe Adresse :
public class Adresse extends JavaScriptObject {
protected Adresse() {}
public final native String getRue()/*-{ return this.rue; }-*/;
public final native int getCodePostal()/*-{ return this.codePostal; }-*/;
public final native String getVille()/*-{ return this.ville; }-*/;
}
On reconnait simplement ici le code précédent enrichi de quelques notions :
- Les classes étendent de la classe JavaScriptObject
- Les méthodes ont été mises à la sauce JSNI : elles sont natives, le corps de la méthode doit être encadré par /*-{ et }-*/
- Ce que l'on appréciera surtout c'est le code JavaScript à l'intérieur des méthodes. Il s'agit, en effet, de la manipulation d'un objet JSON. Il suffit ici simplement que le this représente une structure JSON pour que ce code fonctionne.
On remarquera aussi le retour de la méthode getAdresse() de la classe Personne qui correspond au type Adresse défini juste après. On a bien cette notion de composition entre Adresse et Personne.
Mais où et comment manipuler ces objets ?
Imaginons que nous ayons donc cette fameuse variable personne en JavaScript comme indiqué au début de ce billet (la structure JSON). Pour encapsuler ce code dans nos objets fraichement créés, il suffit simplement de créer et d'appeler une méthode :
private native Personne createPersonne()/*-{
return eval($wnd.personne);
}-*/;
On utilise ici la fonction JavaScript eval() qui interprète la structure JSON. Le résultat de cette évaluation est retourné par la méthode createPersonne(). Comme je précisé plus haut, des méthodes implémentées à partir de JSNI peuvent retourner des objets JSO représentant un élément JavaScript. C'est bien le cas ici puisque je retourne Personne qui est un JavaScriptObject ! Je définis ainsi le this, que j'avais laissé de côté tout à l'heure lors de la présentation du code de Personne et de Adresse, avec la structure JSON portée par la variable personne.
Maintenant, n'importe où dans mon code nous pouvons faire en Java
Personne p = createPersonne() ;
Window.alert(p.getNom) ;
Qui m'affichera ici Martin dans une popup.
On voit donc qu'il est très simple d'encapsuler des objets JavaScript dans des objets Java équivalent et de garder toute la logique des données manipulées. Mais dans quel cas cela peut-il vraiment me servir ?
Il est vrai qu'avec GWT, toute la communication avec la partie serveur consistant à récupérer des données est construite autour de l'API RPC. Ce framework fait tout le travail de traduction afin de faire communiquer les deux mondes.
Réfléchissons un instant à la phrase précédente : « . la communication avec la partie serveur consistant à récupérer des données. ». Il est vrai que dans une architecture à la mode RIA, toute la présentation est gérée par le code client (ici le JavaScript généré par GWT) et ce code client n'a donc besoin que d'une couche service pour récupérer des données. Tout le traitement applicatif est géré côté présentation. Dans beaucoup de cas, il s'agit donc de ressources à récupérer et à manipuler dans le but de mettre à jours des IHM. Mais quelle technologie du web permet simplement d'exposer des ressources et de laisser le choix de la représentation des données lors du transfert au client ? oui oui, c'est bien de REST que je veux parler !
Sans épiloguer sur les notions de SOP (Same Origin Policy), on peut très bien imaginer notre couche service implémentée en REST plutôt qu'avec les servlets à la mode RPC de GWT. D'autant plus que cela est très simple à faire en Java en utilisant JAX-RS. JAX-RS utilise par ailleurs JAX-B pour la transformation des objets Java, en format transférable. et JAX-B est capable de transformer ces objets en XML, mais également et surtout en JSON !!
Côté client, on peut ainsi utiliser l'objet RequestBuilder fourni par GWT pour faire des requêtes http.
Je suis bien évidemment conscient que REST ne va certainement pas remplacer de facto nos services RPC, mais dans des architectures où la couche service est implémentée de cette manière, c'est la manière idéale de récupérer et de manipuler facilement des objets JSON !
Pour voir un exemple réel de cette utilisation, je vous invite à aller regarder la nouvelle architecture de JIRA 4 qui est basée sur la notion de gadget et de service REST. Les gadgets sont ici initialement créés en JavaScript, mais je ne vais pas vous convaincre de l'utilité de GWT pour faire ce travail ![]()
Billet rédigé par Romain Hochedez (romain.hochedez@dng-consulting.com)
Citation :
"La manipulation de cet objet est cependant très limitée : je ne peux pas créer d'instance d'un JSO dans mon code Java, il s'agit vraiment d'un objet encapsulant du JavaScript et qui permet de franchir la barrière des deux mondes."
Il m'est arrivé de devoir créer des JSO sans avoir de JSON suite à certaines problématiques. Il est vrai qu'il est normalement impossible de créer une instance d'un JSO depuis le code Java mais voici une façon de procéder pour contourner cette limitation pour un gain de productivité en développement. Je répète que cette solution est due à un problème très spécifique....
Tout d'abord créer un classe qui servira a créer les instances puis ajouter ces methodes:
public class JSObjectHelper {}Puis rajouter une méthode qui créé un objet :public final static native <T extends JavaScriptObject> T createObject() /*-{ return {}; }-*/;encore mieux avec la généricité! Il vous suffirait de créer une classe qui hérite de JavaScriptObject . Du coup avec la généricité ça deviendrait :
public final static native <T extends MySuperBusinessClass> T createObject() /*-{ return {}; }-*/Cette solution est meilleure car elle permet de contourner les erreurs avec notamment les tableaux.... Du coup vos classes métier doivent hériter de MySuperBusinessClass.. :)
Après on peut également contourner la limitation de Java sur les appels de méthode par passage de copie de référence:
public final static native <T extends JavaScriptObject> void setObjectAttribute(T jsob, String attribut, String value) /*-{ jsob[attribut] = value; }-*/;Magique! on ajoute à la volée des valeurs sur notre objet JSO. Par contre rien n'empêche de rajouter des valeurs qui n'existent pas dans notre objet métier.... donc à utiliser avec parcimonie :) Voila , en espérant que ceci éclaire un peu plus le fonctionnement des JSO pour certains...