GWT : Génération dynamique de proxy

Publié le 11/10/2007, par Bruno Marchesson dans Architecture | Comments Off

Génération de proxy coté serveur : un classique
La génération dynamique de proxy en Java est un mécanisme relativement nouveau, même si la notion même de proxy est standardisée depuis Java 1.4. L’émergence de CGLIB tout d’abord, puis de Javassist, a permet le développement de nombreuses librairies manipulant de simples POJO (donc sans héritage technique). En fait, l’astuce consiste pour ces librairies a modifier votre POJO, par héritage, en lui ajoutant dynamiquement les propriétés nécessaires au bon fonctionnement de la librairie:
Proxy mechanism
C’est bien entendu le mécanisme utilisé par Hibernate par exemple pour instrumentaliser vos POJO et y ajouter les infos dont il a besoin.

Tentative d’évasion
Si astucieux soit-il, ce mécanisme est source de graves ennuis dès qu’il s’agit de quitter la JVM sur laquelle a été généré le proxy. Essayer par exemple d’envoyer un objet Hibernate par JMS : si le lecteur ne possède pas l’archive Hibernate dans son classpath, c’est l’erreur de désérialisation assurée…

Le problème se pose donc pour GWT, que l’on peut assimiler _ entre autre _ à une JVM Javascript. Imaginons une classe ‘Message’ que nous souhaitons instrumenter afin d’y ajouter des informations de débug, de sécurité, etc…
Pour cela, il faut étendre la classe Message en MessageProxy, grâce à Javassist par exemple :


ClassPool pool = ClassPool.getDefault();
CtClass proxyClass = pool.makeClass("MessageProxy");
proxyClass.setSuperclass(pool.get("Message"));
...
return proxyClass.toClass(getClassLoader());

Tant que la classe reste coté serveur, pas de souci. Par contre, toute tentative d’envoyer cette classe sur la lune… euh, coté client ;) se heurtera à une erreur de sérialisation :
com.google.gwt.user.client.rpc.SerializationException: Type ‘<Proxy>’ was not included in the set of types which can be serialized by this SerializationPolicy. For security purposes, this
type will not be serialized.

Serialization Policy : vos papiers…
En effet, en GWT 1.4, la politique de sérialisation a changé. Afin d’éviter un héritage technique hideux avec IsSerializable, l’option a été prise de lister à la compilation les classes sérialisables (contenues dans les fichiers gwt.rpc générés). D’où un conflit évident avec la génération dynamique de proxy…
De plus, le processus de sérialisation GWT est foncièrement statique. Même si la politique de sérialisation n’empêchait pas l’envoi de proxy, les données supplémentaires du Proxy ne seraient tout simplement pas envoyées sur le client, car la classe Javascript ne contient pas les champs correspondant à ces données.
Serialization_js
Il faut donc générer une classe Javascript qui soit le pendant de notre proxy. Pour cela, GWT a une solution : les Generator. Ce mécanisme, relativement peu documenté, s’appuie sur le principe de deferred binding utilisé notamment par l’internationalisation de la librairie.

Pour faire court, la mise en place de generators nécessite 3 éléments :

  • Le générateur de code proprement dit : il s’agit d’une classe héritant de Generator qui manipule et enrichit les métadonnées de classe :

...
ClassSourceFileComposerFactory composerFactory =
    new ClassSourceFileComposerFactory("", "MessageProxy");
composerFactory.setSuperclass("Message");
SourceWriter sourceWriter =
    composerFactory.createSourceWriter(context, printWriter)
...
sourceWriter.commit(logger);
  • La déclaration du générateur dans le fichier XML de GWT (exemple) : elle associe le générateur à une ou plusieurs classes, voire à toutes celles assignables pour une classe abstraite ou interface donnée. Ce qui signifie entre autres que toutes vos classes « proxyfiables » doivent soit être explicitement et individuellement nommées dans le fichier de configuration XML de GWT, soit implémenter une interface vide qui sert de marqueur :
<generate-with class="net.sf.hibernate4gwt.rebind.GwtProxyGenerator">
  <when-type-assignable class="net.sf.hibernate4gwt.proxy.IProxy" />
</generate-with>
  • Un appel explicite à “GWT.create(Message.class)” dans le code de l’application cliente. Ce dernier point est particulièrement important car le compilateur GWT est particulièrement bien optimisé : sans appel à la méthode de création susdite, il en déduit que la génération du proxy est inutile et donc votre générateur n’est pas appelé. Un bon moyen de vérifier est de surveiller la console de lancement GWT : les classes instrumentalisées y apparaissent explicitement

generator binding

Pour plus de détails sur la mise en place de générateurs, je vous renvoie directement au billet correspondant de l’excellent blog Timepedia ou au non moins l’excellent « GWT in action » qui traitent du sujet en profondeur.

Generator coté serveur : une mauvaise idée
J’avoue avoir essayé de récupérer le proxy généré par GWT dans mon code Java. Après tout, en hosted-mode, ma classe est bien instanciée en Java, non ?
En fait, les Generator GWT sont une technologie clairement orienté couche cliente. Un appel à GWT.create coté serveur soulève une automatiquement une exception, et hacker le hosted-mode pour récupérer la classe Java impliquerait que l’application embarque le moteur Tomcat embarqué utilisé dans ce mode. Quand je vous disais que c’était une mauvaise idée

Faisons le point…
Donc pour envoyer un proxy serveur coté client en GWT, il faut donc :

  • Créer le proxy coté serveur (avec Javassist par exemple)
  • Créer ce même proxy coté client avec un Generator GWT
  • Forcer la création des proxys coté client en appelant explicitement GWT.create

Bien entendu, il est fortement conseillé de factoriser la génération de proxy dans une même classe, la cohérence du proxy coté serveur et client étant cruciale pour le passage d’une JVM (classique) à l’autre (Javascript).

Quelques détails d’implémentation
Sur le papier, les étapes ci-dessus devraient suffire à faire créer des proxy dynamiques. En pratique, il reste un dernier petit détail à régler…
Vous vous souvenez de la SerializationPolicy dont j’ai parlé il y a quelques lignes ? Depuis la version 1.4, le compilateur génère une liste de classes autorisées à être envoyées par RPC (et donc sérialisées en Javascript), stockés dans un fichier ‘gwt.rpc’.

Première constatation : les proxys créés par notre Generator y apparaissent explicitement s’ils font partie de la signature des méthodes de notre RemoteService, ce qui est assez surprenant pour une technologie orientée cliente !

Deuxième constatation, plus désagréable celle-ci : l’implémentation de SerialializationPolicy génère sa « white-list » au premier appel. Pour cela, elle tente d’instancier toutes les classes contenues dans ses fichiers ‘gwt.rpc’ associé. Il faut donc créer les proxy avant cette vérification, sous peine de faire systématiquement échouer l’appel à cause d’une ClassNotFoundException sur celui-ci…

Là aussi, il existe plusieurs solutions :

  • Générer au lancement coté serveur tous les proxys nécessaires. Dans le cas d’une interface marqueur, cela pose différents problèmes (cf. ici par exemple) assez délicats à gérer.
  • Plus simplement, j’ai développé un ClassLoader qui wrappe le ClassLoader par défaut et qui génère mes proxy à la première demande de chargement.
public Class loadClass(String name) throws ClassNotFoundException
{
  if (isProxy(name))
  {
  //	Get source class name
  //
    String sourceClassName = getSourceClassName(name);
    Class sourceClass = _wrappedClassLoader.loadClass(sourceClassName);
  //	Generate proxy
  //
    return generateProxyClass(sourceClass);
  }
  else
  {
  //	Load class
  //
    return _wrappedClassLoader.loadClass(name);
  }
}

Et ça marche…
J’ai fait plusieurs tests (en fait, j’utilise cette technique pour remplacer un héritage technique d’hibernate4gwt par une simple interface de marquage) et cela semble fonctionner dans le cas qui m’intéresse.
Par contre, il existe quelques points à améliorer avant de rendre un tel mécanisme réellement transparent, à cause notamment des appels explicites à GWT.create (je n’ai pas encore réussi à berner le compilateur GWT ( ) et de l’enrobage nécessaire du ClassLoader.
Avis aux amateurs !

Comments are closed.