Les besoins de dynamisme en programmation sont multiples, un des grand classique est le chargement tardif. Lazy loading pour les intimes. Le getter tardif d'un bean ne va chercher la valeur demandée qu'au dernier moment, et uniquement si on le lui demande. Ce comportement est Indispensable à tous les ORM.

Dynamisme

Les langages dynamiques comme Python ou Groovy se régale dans ce genre de tache, mais Java, avec des produits tiers arrive tout à fait à se débrouiller. Il existe deux outils majeurs pour ça, AspectJ, qui a une approche haut niveau, et ASM, qui a une approche bas niveau, en retravaillant directement le bytecode. ASM est utilisé par beaucoup d'outils plus civilisés. Mais ça, c'est l'approche moderne, pendant longtemps, Java s'est contenté des proxys statiques du JDK, et des proxys dynamiques de cglib.

Proxy

Un proxy est un faux objet qui en emballe un vrai, et qui intercepte certaines de ses méthodes. Cglib propose gentiment de proxyfier n'importe quel objet, sans qu'il ait besoin d'implémenter une interface spécifique. C'est un progrès par rapport aux proxys du JDK, mais ça amène une myriade de problèmes. Le premier est que toutes les méthodes sont devenus finales, rendant impossible la création d'une class dérivée. Il salope aussi le nom de la class en la proxyfiant, mais ce n'est pas trop grave, ça. Cerise sur le gateau, la documentation de cglib est dégueulasse, il faut se dépatouiller avec seulement des exemples et des javadocs succinctes. On pourrait ce dire que ce n'est pas grave, cglib a eut son rôle à jouer, remercions le et passons à des jouets plus tolérants et plus modernes. Mais cglib est utilisé dans des projets majeurs comme Hibernate, dans une version périmé, en plus.

Manipuler le bytecode

Manipuler ASM directement est très puissant, mais aussi complexe. Modifier directement des fichiers java, et non du bytecode est une option tentante, Java 6 apporte la possibilité de compiler directement du code java, sans passer par un fichier, et java 7 permet même de modifier le comportement du compilateur. Mais ça, c'est le futur, là, dans le présent, coincé avec un java 5, c'est une autre paire de manche. Groovy peut venir à la rescousse.

Groovy

Groovy fonctionne avec Java 1.4 et la compilation fait parti de ses supers pouvoirs. Le code groovy est transformé en bytecode java, lui permettant ainsi d'utiliser les outils Java canal historique, et de créer des objets 100% java. Groovy est en fait le second outil qui officiellement créer du bytecode java.

Le besoin est simple. On a une class A, avec A_proxy qui est crée par cglib et qui hérite de A. A_proxy est final. On veut pouvoir définir en Groovy, une class B qui hérite de A. Bien sur, tout est dynamique, la moulinette receptionne donc une instance a de la class A, discrétement transformé en instance de A_proxy par cglib, et doit renvoyer une instance de A, avec les attributs de a, mais avec des méthodes qui ont changé. C'est l'objet du projet Comportement.

La class de base de Groovy, l'équivalent de du Object de Java, GroovyObject, contient tout ce qu'il faut gérer l'équivalent d'un proxy. Le compilateur de Groovy permet de modifier le code avant de le compiler. C'est cette fonction qui permet à Grails de bricoler les objets domaines en leur rajoutant un attribut identifiant et un hashcode. Dans notre cas, ce qui est intéressant, c'est que l'on peut déclarer la class dont hérite un objet. On a donc une class B, qui hérite de A. Le bricolage conciste à patcher une class B_proxy qui a un attribut, l'instance de A_proxy. B_proxy hérite de A. B est patché pour hériter de B_proxy.

Voici le code Groovy qui emballe le cglib.

[java]
import org.codehaus.groovy.runtime.InvokerHelper

class GroovyProxyWrapper implements GroovyInterceptable, GroovyProxyfier {
  Object __proxyfiedObject
  Object invokeMethod(String name, Object args){
    def metaClass = InvokerHelper.getMetaClass(this)
    MetaMethod metaMethod = metaClass.getMetaMethod(name, args)
     if(__proxyfiedObject.metaClass.superClasses.contains(metaMethod.declaringClass)) {
       return __proxyfiedObject.invokeMethod(name, args)                 
    }
    return metaClass.invokeMethod(this, name, args)
  }
}

GroovyProxyfier est une simple interface pour aider Java dans tout ce dynamisme.

[java]
public interface GroovyProxyfier {
  public void set__proxyfiedObject(Object object);
}

Donc, en receptionnant l'objet a, on patch la class GroovyProxyWrapper pour la transformer en class sur mesure GroovyProxyWrapperA, qui étends A. Le code de B est patché pour ne plus étendre A, mais GroovyProxyWrapperA. B étends donc indirectement A, la morale est sauve. Il suffit donc maintenant d'instancier B, et de lui setter a grâce à l'interface GroovyProxyfier.

Bon, la méthodologie est proche de celles du Dr Frankenstein, mais cglib est vaincu.

jMockIt propose de greffer une méthode d'une class vers une autre class. C'est aussi une piste à explorer.