Cours 2-3 Architectures réflexives
Transcription
Cours 2-3 Architectures réflexives
De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN : Architectures logicielles pour l’auto-adaptabilité dynamique Cours 2-3 Architectures réflexives c 2004-2011 Jacques Malenfant � Université Pierre et Marie Curie UFR 919 Ingénierie [email protected] c 2004-2011 Jacques Malenfant � ALADYN 1 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java c 2004-2011 Jacques Malenfant � ALADYN 2 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Table des matières 1 De la réflexion structurelle à la réflexion comportementale 2 Réflexion structurelle et réification du code en Java 3 Réflexion de comportement en Java c 2004-2011 Jacques Malenfant � ALADYN « Simple things should be simple, complex things should be possible. » Alan Kay [Kay93] créateur de Smalltalk 3 c 2004-2011 Jacques Malenfant � ALADYN 4 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets Formes de réflexion (rappel) 1 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle Réflexion structurelle Réflexion structurelle dans les langages à objets Tout ce qui touche aux aspects plus statiques, comme le contenu du programme ou les types de données abstraits du langage. Réflexion de comportement Réflexion de comportement dans les langages à objets Réflexion de comportement 2 Réflexion structurelle et réification du code en Java 3 Réflexion de comportement en Java c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN Tout ce qui touche aux aspects plus dynamiques, liés à l’état d’exécution et à la façon d’exécuter les programmes. 5 Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN 6 Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets Principaux aspects 1 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle Réification du code du programme. Réflexion structurelle dans les langages à objets Réification des entités manipulées dans un programme, c’est-à-dire tous les types de données abstraits fournis par le langage (flottants, tableaux, listes, etc.). Réflexion de comportement Réflexion de comportement dans les langages à objets 2 Réflexion structurelle et réification du code en Java 3 Réflexion de comportement en Java c 2004-2011 Jacques Malenfant � ALADYN Exemples : Pouvoir accéder à la classe d’un objet à l’exécution et l’examiner pour connaître la signature, voire le code, des méthodes qui y sont déclarées. Si le langage fournit un type de données table de hachage, pouvoir examiner les valeurs de ce type, et en modifier la définition (représentation, méthodes, ...). 7 c 2004-2011 Jacques Malenfant � ALADYN 8 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java À quoi ça sert ? I Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets À quoi ça sert ? II Manipulation du code du programme : examiner ou modifier le code du programme pendant l’exécution. Exemples : Exemples : Sérialisation/désérialisation. Persistence sur disque. Choix d’algorithmes de mise en oeuvre en fonction de profils d’utilisation (exemple de l’abstraction fenêtre...). etc. Ajouter du code pour la sécurité. Ajouter ou retirer du code pour vérifier des assertions. Modifier des envois de message pour utiliser le RMI. Ajouter du code pour générer des traces, ou des informations sur l’exécution etc. Manipuler des types de données : examiner les valeurs des types complexes dans leur représentation interne. c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN 9 Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN 10 Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets Réflexion structurelle et langages à objets 1 De la réflexion structurelle à la réflexion comportementale Les langages à objets se prêtent particulièrement bien à la réflexion. Réflexion structurelle La notion même d’objet est réificatrice. Réflexion structurelle dans les langages à objets Dans tout langage à objets, un objet est une entité de plein droit. L’analyse et le développement par objets procède de l’idée même de rendre manipulable lors de l’exécution les concepts et entités du domaine d’application, en en faisant des classes et des objets (Personne, Voiture, ...). Dans la plupart des langages à objets, l’objet est l’entité de base à partir de laquelle sont construites toutes les valeurs des autres types de données manipulées dans le langage. Il y a des exceptions notables, comme les types primitifs et les tableaux en Java, qui ne sont que partiellement réifiés (i.e. ils restent, au moins partiellement, des boîtes noires), même avec la conversion automatique depuis Java 5.0. Réflexion de comportement Réflexion de comportement dans les langages à objets 2 Réflexion structurelle et réification du code en Java 3 Réflexion de comportement en Java c 2004-2011 Jacques Malenfant � ALADYN 11 c 2004-2011 Jacques Malenfant � ALADYN 12 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réflexion structurelle et réification Classes comme entités de plein droit Structure de données à manipuler : les objets. Faire en sorte que les classes soient représentées par des objets à l’exécution assure : Objets sont décrits par des classes. de pouvoir utiliser dynamiquement le lien instance-de de tout objet pour retrouver l’objet représentant sa classe d’instantiation ; Exemple (Java) : Donc, pour manipuler sans contrainte les objets à l’exécution, il faut aussi pouvoir manipuler, et donc réifier, les classes. Solution : représenter les classes comme des objets donc accessibles à l’exécution. ⇒ les classes deviennent elles aussi de entités de plein droit. Point p = new Point(0.0, 0.0) ; Class<?> point = p.getClass() ; de pouvoir utiliser les classes comme n’importe quel autre objet, dont leur envoyer des messages. Smalltalk, CLOS, Java (mais limité, voir plus loin), beaucoup de langages expérimentaux. c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN 13 Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réification des classes ALADYN 14 Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets Forme affaiblie de réflexion structurelle : RTTI de C++ Mais quelle est la classe d’une classe lorsqu’elle est vue comme un objet ? C++ offre une forme limitée de réflexion structurelle par sa bibliothèque RTTI (pour Run-Time Type Information d’information sur les types à l’exécution. Pour réifier complètement les classes, il faut aussi réifier leur description sous forme de classe également. Les classes ne sont pas des objets. C’est ainsi qu’on obtient une métaclasse, c’est-à-dire une classe de classes, vues comme des objets. Le compilateur construit un ensemble de données accessibles (en lecture) à l’exécution pour introspecter le contenu des classes. Avant d’explorer toutes les conséquences de ce concept, étudions des formes plus limitées de réflexion de structure en langages à objets : cas de C++ et, puis plus longuement de Java. c 2004-2011 Jacques Malenfant � Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets ALADYN 15 c 2004-2011 Jacques Malenfant � ALADYN 16 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Information dynamique sur les types en C++ La structure type_info standard class type_info { private : type_info ( const type_info &) ; type_info& operator =( const type_info &) ; public : v i r t u a l ~ type_info ( ) ; i n t operator ==( const type_info &) const ; i n t operator ! = ( const type_info &) const ; i n t b e f o r e ( const type_info &) const ; const char ∗ name ( ) const ; } L’opérateur typeid permet d’identifier le type exact d’un objet à l’exécution. Exemple : if (typeid(r) == typeid(Circle)) { ... } La structure type_info agit comme un crochet (hook ) pour avoir des informations dynamiques complémentaires sur un type. Une implantation peut ajouter d’autres informations par une extension appelée extended_type_info. c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets 17 Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN 18 Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets Domaine de définition 1 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle Réflexion structurelle dans les langages à objets Tout ce qui touche aux aspects plus dynamiques, liés à l’état d’exécution et à la façon d’exécuter les programmes. Réflexion de comportement Principaux éléments à réifier : Réflexion de comportement dans les langages à objets 2 Réflexion structurelle et réification du code en Java 3 Réflexion de comportement en Java c 2004-2011 Jacques Malenfant � ALADYN les structures de données de l’évaluateur : liaison des variables, pile d’exécution, mémoire (gc), etc. l’évaluateur lui-même : procédures pour l’évaluation des programmes. 19 c 2004-2011 Jacques Malenfant � ALADYN 20 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets Utilisations classiques 1 Réflexion structurelle Description de l’implantation : allocation des entités, gc, extensions incrémentales, ré-implantation du système en entier. Réflexion structurelle dans les langages à objets Surveillance : inspection, analyse et modification dynamique du code dans le langage lui-même. Réflexion de comportement Réflexion de comportement dans les langages à objets Interfaçage et déverminage : implantation d’outils dans le langage lui-même, interopérabilité avec des programmes écrits dans d’autres langages. Auto-réorganisation : capacité à apprendre à partir de l’exécution du programme, de manière à améliorer sa cohérence et ses performances. c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN De la réflexion structurelle à la réflexion comportementale 21 Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets 2 Réflexion structurelle et réification du code en Java 3 Réflexion de comportement en Java c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java De la structure au comportement pour les objets I ALADYN 22 Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets De la structure au comportement pour les objets II En POO, outre les aspects déjà vus dans les langages classiques, la réflexion structurelle doit prendre en compte les objets et, en cela, elle est maîtrisée par les modèles de métaclasses, classes et instances. Pour introduire de la réflexion structurelle, il faut que la description des objets, les classes, soient accessibles à l’exécution, et que ces deux opérations, allocation et initialisation, soient modifiables par le programme. En réalité, la réflexion structurelle maîtrise l’équation : Le modèle d’ObjVLisp réifie ces deux opérations sous forme de méthodes, organise leur définition par les entités du noyau et leur redéfinition par les métaclasses et classes définies par le programmeur. new = allocate ◦ initialize c’est-à-dire qu’un objet est le résultat d’une allocation suivie d’une initialisation de ses champs. c 2004-2011 Jacques Malenfant � ALADYN 23 c 2004-2011 Jacques Malenfant � ALADYN 24 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réification de l’envoi de message Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets Trois modèles principaux La réflexion de comportement cherche à maîtriser la réification de l’appel de méthode par l’équation : Réflexion de comportement utilise trois modèles principaux : method call = lookup ◦ apply 1 c’est-à-dire qu’un appel est la composition de la recherche de la méthode dans la classe du receveur suivie de l’application de la méthode trouvée sur le receveur. 2 appel de méthode (envoi de message) = composition de recherche de méthode puis application ; réification de ces deux opérations sous forme de méthodes. Bien que la réflexion de comportement en POO soit fondée sur les mêmes principes généraux que ceux de l’approche procédurale, une grande part des travaux s’est fondée sur un équivalent des procédures réflexives visant à réifier l’envoi de message comme fondement de l’exécution des programmes à objets. c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN 3 Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets Application de fonction générique, elle-même réifiée comme une fonction générique (CLOS). c 2004-2011 Jacques Malenfant � 25 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Qui définit ces méthodes ? ALADYN 26 Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets Avantages et inconvénients, première solution Une fois réifiées, il faut décider quelle entité va les définir pour un objet donné. Classe Les méthodes réifiées sont alors définies par la métaclasse (qui définit les méthodes de la classe). Dans le contexte d’un langage réflexif, la réflexion structurelle nous offre déjà deux entités possibles : la classe de l’objet ou la métaclasse de celle-ci. o.m(a1, ..., an) ⇒ o.class.handleMsgOn(o, m, [a1, ..., an]) Maes [Mae87] a proposé d’associer à chaque objet une nouvelle entité appelée méta-objet, qui peut alors définir les méthodes. Toutes les instances d’une classe partage les mêmes méthodes réifiées, ce qui empêche de les particulariser à un objet donné. Une troisième possibilité consiste à réifier le message lui-même, et que ce soit l’objet message qui définisse les méthodes. c 2004-2011 Jacques Malenfant � Opération de traitement de message réifiée sous forme d’une méthode (handleMsg). Modèle rechercher ◦ appliquer (lookup ◦ apply ) ALADYN 27 c 2004-2011 Jacques Malenfant � ALADYN 28 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Avantages et inconvénients, seconde solution Avantages et inconvénients, troisième solution Méta-objet Message Un méta-objet est alors associé à chaque objet et les méthodes réifiées sont définies par la classe du méta-objet. Chaque message est représenté par un objet et les méthodes réifiées sont définies par la classe de cet objet message. o.m(a1, ..., an) ⇒ o.metaobject.handleMsgOn(o, m, [a1, ..., an]) o.m(a1, ..., an) ⇒ (new Message(m, [a1, ..., an])).handleOn(o) L’association étant à l’objet, il devient possible de les particulariser pour chaque objet, ce qui peut être vu comme allant à l’encontre du modèle à classe où toutes les instances d’une classe ont le même comportement. c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN On peut alors particulariser les méthodes pour chaque type de messages, mais rien n’est dit sur le comportement des objets qui perdent alors le contrôle sur la façon de traiter les messages. 29 Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets ALADYN 30 Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets Modèles plus complexes Les approches précédentes sont minimales, pour montrer les principaux choix de conception. On sépare le traitement du message en deux parties : la recherche de la méthode à appliquer, puis l’application de la méthode trouvée. En pratique, concevoir un langage réflexif implique la réification d’un grand nombre d’entités qui coopèrent pour la mise en œuvre du méta-niveau par des envois de messages. Règle de réécriture : L’organisation du méta-niveau d’une manière claire et compréhensible s’est avérée un défi difficile à réussir. o.m(a1, ..., an) ⇒ o.<meta>.lookup(m).applyTo(o, [a1, ..., an]) Ici, <meta> peut être un accès à la classe ou au métaobjet. ALADYN c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Le modèle rechercher ◦ appliquer c 2004-2011 Jacques Malenfant � Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets Cette difficulté a mené à la notion de protocole de méta-objets pour définir le « frameworks » réalisant le méta-niveau d’un langage ou d’un outil. 31 c 2004-2011 Jacques Malenfant � ALADYN 32 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Protocoles de méta-objets En terme de langages à objets « In a language based upon our metaobject protocols, the language implementation itself is structured as an object-oriented program. This allows us to exploit the power of object-oriented programming to make language implementation adjustable and flexible. In effect, the resulting implementation does not represent a single point in the overall space of language designs, but rather an entire region within that space. » « Metaobject protocols are interfaces to the language that give users the ability to incrementally modify the languages behavior and implementation, as well as the ability to write programs within the language. Language that incorporate metaobject protocols blur the distinction between language designer and language user. » Kiczales, des Rivières et Bobrow [KRB91] c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN Kiczales, des Rivières et Bobrow [KRB91] 33 Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java L’ancêtre conceptuel : CLOS ALADYN 34 Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets Le concept de MOP aujourd’hui CLOS = Common Lisp Object Language. Tous les langages et systèmes réflexifs à base d’objets offrent un protocole de méta-objets. Standardisation de l’extension objet de Common Lisp fondée sur le concept central de fonction générique ou multi-méthode. La notion de MOP a surtout eu comme avantage de mettre au devant de la scène la nécessité de concevoir le méta-niveau selon des principes architecturaux clairs, réguliers et accessibles aux programmeurs voulant faire de la réflexion dans leurs programmes. (Le MOP de Smalltalk....) multi-méthode : méthode comportant plusieurs définitions et dont la sélection s’opére de manière à trouver la définition la plus spécifique aux types de tous les paramètres réels, contrairement aux méthodes où la sélection se fait sur le type du receveur uniquement. Par extension, on parle aujourd’hui du MOP offert par des outils spécifiques, comme un compilateur, un chargeur, etc. Exemple : Javassist, un MOP pour la réflexion de comportement au chargement des classes en Java. La définition de sa sémantique a été faite en produisant une implantation de CLOS en CLOS, et ainsi définir ce qu’il est maintenant convenu d’appeler le MOP de CLOS. c 2004-2011 Jacques Malenfant � Réflexion structurelle Réflexion structurelle dans les langages à objets Réflexion de comportement Réflexion de comportement dans les langages à objets ALADYN 35 c 2004-2011 Jacques Malenfant � ALADYN 36 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist Manipulation du code en Java Étude de cas : Javassist 1 De la réflexion structurelle à la réflexion comportementale 1 De la réflexion structurelle à la réflexion comportementale 2 Réflexion structurelle et réification du code en Java 2 Réflexion structurelle et réification du code en Java 3 Manipulation du code en Java Manipulation du code en Java Étude de cas : Javassist Étude de cas : Javassist Réflexion de comportement en Java c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN 3 37 ALADYN 38 Manipulation du code en Java Étude de cas : Javassist Comment rendre un accès au code Comme toute implantation de langage, la machine virtuelle a bien sûr accès à une représentation du code : le contenu des fichiers .class est chargé dans la machine virtuelle via des chargeurs (« loaders »). Les classes sont représentées par des objets de plein droit, mais ces objets ne sont pas pour autant des classes mais plutôt des objets représentant des classes sans s’identifier à elles. L’objet obtenu par Class.class est lui-même une instance de Class. Le contenu d’un fichier .class est standardisé. Il est du même niveau que la représentation utilisée par Smalltalk : du code octal d’une machine virtuelle. À la base, plutôt un système de représentation des types à l’exécution, analogue au RTTI de C++, mais en beaucoup plus complet. Plusieurs outils ont donc été conçus et implantés pour donner un accès au code des classes via son fichier .class. Une des limitations importantes de l’API Reflection est de ne pas fournir un accès au code des méthodes, ce qui est une partie intégrale de la réflexion structurelle. ALADYN c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist Forme de réflexion structurelle offerte par Java c 2004-2011 Jacques Malenfant � Réflexion de comportement en Java Ces outils s’interposent généralement entre la machine virtuelle et les fichiers .class pour donner aux programmes la possibilité de modifier le code des classes lors de leur chargement. 39 c 2004-2011 Jacques Malenfant � ALADYN 40 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist BCEL et ASM Réflexion au chargement Les bibliothèques BCEL et ASM partagent le même objectif général : représenter le contenu des fichiers .class et en permettre la manipulation par le programme lui-même. Outre BCEL et ASM, il existe d’autres bibliothèques, comme Javassist, qui partagent le principe de modification lors du chargement, ce qui a donné naissance au concept de réflexion au chargement. Toutes les deux offrent une API en termes de code octaux pour cette manipulation. Une approche un peu différente consiste à donner accès au processus de traduction du code octal vers le langage du processeur physique, réalisé par le compilateur juste-à-temps. La principale différence entre les deux bibliothèques tient à la représentation adoptée : BCEL utilise une représentation par objets pure, ce qui peut générer un grand nombre d’objets que l’on parcourt ensuite relativement lentement en suivant les identificateurs d’objets. ASM adopte une représentation de type tableau, tout en offrant cependant des itérateurs masquant complètement cette représentation pour donner l’illusion d’une représentation par objets. c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN La niveau d’abstraction dans les modifications est alors cependant très bas. La limitation générale reste le fait que peu de modifications sont possibles après la phase de chargement de l’application. 41 c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist API JPDA, et tutti quanti I ALADYN 42 Manipulation du code en Java Étude de cas : Javassist API JPDA, et tutti quanti II L’API java.lang.instrumentation de Java 6.0 accepte le changement (rechargement ou la transformation) à chaud des classes, ce qui étend les limites de ce qu’on peut faire dynamiquement. Cependant, il reste plusieurs limitations importantes aux transformations possibles, qui peuvent changer l’implantation des méthodes, mais ne peuvent L’API JPDA (« debugging ») introduit de nouvelles possibilités, comme le rechargement à chaud du code des classes et la récupération par événements des informations de la pile d’exécution. L’inconvénient tient au fait qu’on suit une philosophie debugging avec interruption du programme en cours et qu’il faut lancer la machine virtuelle dans un mode ouvert et moins efficace. ni changer les membres de la classes (champs ou méthodes), ni la signature des méthodes, ni l’héritage de la classe. D’autres API arrivent avec un même principe de récupération d’information par événements, comme l’API JMX (« Java Management Extensions ») et le nouveau « package » java.lang.instrumentation de Java 5.0. c 2004-2011 Jacques Malenfant � Manipulation du code en Java Étude de cas : Javassist ALADYN Cette nouvelle API promet des modifications de classes à chaud plus efficaces que le « hotswap » de JPDA. 43 c 2004-2011 Jacques Malenfant � ALADYN 44 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist Manipulation du code en Java Étude de cas : Javassist Une bibliothèque pour manipuler le code octal I 1 2 De la réflexion structurelle à la réflexion comportementale Comme plusieurs autres outils du même genre (BCEL, ASM), Javassist est un bibliothèque permettant de manipuler les classes sous leur forme compilée. Réflexion structurelle et réification du code en Java Manipulation du code en Java Le format manipulé est celui des fichiers .class de Java. Étude de cas : Javassist 3 Javassist se caractérise par le fait que les manipulations autorisées se font au moment du chargement : Les documents sur Javassist parlent de « compile-time », c’est-à-dire à la compilation. C’est en fait un abus de langage, puisque la classe est déjà compilée ; il s’agit plutôt de réflexion au chargement (« load-time »). Réflexion de comportement en Java c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN 45 c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist Une bibliothèque pour manipuler le code octal II ALADYN 46 Manipulation du code en Java Étude de cas : Javassist Principes généraux Lors du chargement des classes, il est possible d’intervenir sous la forme d’un filtre entre le contenu des fichiers .class et la définition de la classe dans la machine virtuelle. Javassist offre une interface de haut niveau permettant de manipuler le code sous forme de source et non de code octal. Javassist propose une bibliothèque pour réifier le contenu des fichiers .class sous forme d’objets. Initialement, Javassist se limitait à des manipulations au chargement des classes, mais il peut être utilisé conjointement avec l’API JPDA et la nouvelle API java.lang.instrumentation pour modifier les classes dynamiquement. Chaque classe est représentée par un unique objet, instance de la classe CtClass, pour « compile-time class », version au chargement de la classe Class de Java. Les manipulations sont autorisées tant que la classe n’est pas mise à disposition de la machine virtuelle ; ensuite, la classe est gelée et rendue inaccessible pour Javassist 2 . 2. Sauf utilisation des API JPDA ou java.lang.instrumentation en Java 6.0. c 2004-2011 Jacques Malenfant � ALADYN 47 c 2004-2011 Jacques Malenfant � ALADYN 48 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist La notion de ClassPool Création d’un collecteur Les instances de CtClass sont créées par le truchement d’un collecteur de classes instance de ClassPool. ClassPool p o o l = ClassPool . g e t D e f a u l t ( ) ; / / p a t r o n s i n g l e t o n CtClass cc = p o o l . g e t ( " t e s t . Rectangle " ) ; cc . s e t S u p e r c l a s s ( p o o l . g e t ( " t e s t . P o i n t " ) ) ; / / change l a s u p e r c l a s s e cc . w r i t e F i l e ( ) ; / / e c r i t u r e de l a c l a s s e m o d i f i e e Le collecteur de classe permet de créer des instances de CtClass en accédant à des fichiers .class ou directement à des tableaux de codes octaux respectant le même format. La méthode toBytecode() retourne un tableau de codes octaux plutôt que d’écrire le fichier de classe. Le principal rôle du collecteur est d’assurer l’unicité de l’instance de CtClass représentant une classe donnée, de manière à maintenir la consistence des transformations de la classe faites par intercession. c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist ALADYN 49 c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist Chemin de classes I ALADYN 50 Manipulation du code en Java Étude de cas : Javassist Chemin de classes II La méthode insertClassPath permet d’ajouter au chemin des classes utiliser par un ClassPool pour charger les classes par défaut il utilise le chemin de la machine virtuelle sous-jacente : La méthode makeClass permet de gérer directement l’accès à un fichier .class connu : ClassPool p o o l = ClassPool . g e t D e f a u l t ( ) ; pool . insertClassPath ( " / usr / l o c a l / j a v a l i b " ) ; ClassPool cp = ClassPool . g e t D e f a u l t ( ) ; InputStream i n s = an i n p u t stream f o r r e a d i n g a class f i l e ; CtClass cc = cp . makeClass ( i n s ) ; Il est également possible de fournir directement une classe compilée sous la forme d’un tableau d’octets : ClassPool cp = ClassPool . g e t D e f a u l t ( ) ; byte [ ] b = <a byte a r r a y > ; S t r i n g name = <class name> ; cp . i n s e r t C l a s s P a t h (new ByteArrayClassPath ( name , b ) ) ; CtClass cc = cp . g e t ( name ) ; c 2004-2011 Jacques Malenfant � ALADYN 51 c 2004-2011 Jacques Malenfant � ALADYN 52 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist Définition d’une nouvelle classe Les ClassPool sont des espaces de nommages On utilise makeClass. Pour créer une classe Point sans membres, on fait : Ils peuvent être imbriqués de pools parents à pools fils. ClassPool p o o l = ClassPool . g e t D e f a u l t ( ) ; CtClass cc = p o o l . makeClass ( " P o i n t " ) ; ClassPool p a r e n t = ClassPool . g e t D e f a u l t ( ) ; ClassPool c h i l d = new ClassPool ( p a r e n t ) ; Il est également possible de lire une classe puis de la renommer pour former une nouvelle classe : ClassPool p o o l = ClassPool . g e t D e f a u l t ( ) ; CtClass cc = p o o l . g e t ( " P o i n t " ) ; CtClass cc1 = p o o l . g e t ( " P o i n t " ) ; / / cc1 e s t i d e n t i q u e a cc . cc . setName ( " P a i r " ) ; CtClass cc2 = p o o l . g e t ( " P a i r " ) ; / / cc2 e s t i d e n t i q u e a cc . CtClass cc3 = p o o l . g e t ( " P o i n t " ) ; / / cc3 n ’ e s t pas i d e n t i q u e a cc . c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN 53 CtClass cc = . . . cc . w r i t e F i l e ( ) ; cc . detach ( ) ; ; c 2004-2011 Jacques Malenfant � ALADYN 54 Manipulation du code en Java Étude de cas : Javassist Réflexion au chargement I Si les classes à modifier sont connues à l’avance, la méthode la plus simple consiste à écrire un programme avec Javassist qui va faire ces modifications. Ce programme va : 1 Récupérer un objet CtClass en appelant ClassPool.get(). 3 Si on charge beaucoup de classes, on peut les écrire puis les retirer du pool avec detach : De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist Relation entre ClassPool et ClassLoader 2 Manipulation du code en Java Étude de cas : Javassist Si les classes à modifier ne sont connues qu’au lancement ou au cours de l’exécution du programme qui les utilise, le programmeur doit faire collaborer les pools de Javassist et le chargeur de classes de Java. Modifier cet objet. Appeler writeFile() sur cet objet de manière à modifier le fichier .class correspondant. Dans un premier temps, seules les classes devant modifier les classes du programme doivent être chargés par le chargeur Java (le méta-niveau). Le programme utilisant les classes modifiées peut ensuite être lancé, indépendamment du programme de modification. Cela se rapproche d’une réflexion à la compilation. c 2004-2011 Jacques Malenfant � ALADYN 55 c 2004-2011 Jacques Malenfant � ALADYN 56 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist Réflexion au chargement II Le chargeur javassist.Loader Pour faciliter les choses, Javassist offre son propre chargeur de classes permettant de charger des classes à partir d’un pool Javassist : Les classes à modifier (le niveau de base) sont d’abord chargées par un chargeur lié à Javassist ; ce n’est qu’après leur modification qu’elles pourront être chargées par le chargeur Java pour devenir exécutables, et alors elles sont gelées à toute modification. import j a v a s s i s t . ∗ ; import t e s t . Rectangle ; public class Main { public s t a t i c void main ( S t r i n g [ ] args ) throws Throwable { ClassPool p o o l = ClassPool . g e t D e f a u l t ( ) ; Loader c l = new Loader ( p o o l ) ; / / chargeur J a v a s s i s t CtClass c t = p o o l . g e t ( " t e s t . Rectangle " ) ; c t . setSuperclass ( pool . get ( " t e s t . Point " ) ) ; / / m o d i f i c a t i o n Class c = c l . l o a d C l a s s ( " t e s t . Rectangle " ) ; / / chargement Java O b j e c t r e c t = c . newInstance ( ) ; / / u t i l i s a t i o n ... } } Cela amène une organisation où le programme utilisateur et le programme d’instrumentation (modifiant les classes du programme utilisateur) sont clairement séparés. c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN 57 ALADYN 58 Manipulation du code en Java Étude de cas : Javassist Interception au chargement Javassist prévoit également un mécanisme basé sur un patron Observateur pour s’interposer entre les fichiers .class et la machine virtuelle de manière à réaliser des transformations au chargement. Un transformateur est un objet dont la classe implante l’interface Translator. Cette interface déclare la méthode onLoad qui est appelée avec le collecteur et le nom de la classe avant d’aller lire le fichier .class. Il faut donc définir une classe de transformation qui réalise les modifications désirées à la classe dans sa méthode onLoad, puis attacher une instance de cette classe au chargeur de classes de Javassist. À partir de là, cette méthode onLoad sera appelée à chaque fois qu’une classe est chargée. Après modification des classes, la méthode toClass() de la classe CtClass fournit un autre moyen très simple pour convertir une instance de CtClass en une instance de java.lang.Class. L’effet de bord de cette opération est de charger la nouvelle classe dans Java pour l’utilisation dans le programme de base. Il existe une version de toClass prenant en paramètre un chargeur instance de Loader qui force le chargement dans ce chargeur. ALADYN c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist La méthode toClass() de CtClass c 2004-2011 Jacques Malenfant � Manipulation du code en Java Étude de cas : Javassist 59 c 2004-2011 Jacques Malenfant � ALADYN 60 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist L’interface Translator Une transformation simple : rendre publique public class M a k e P u b l i c T r a n s l a t o r implements T r a n s l a t o r { void s t a r t ( ClassPool p o o l ) throws NotFoundException , CannotCompileException { } void onLoad ( ClassPool pool , S t r i n g classname ) throws NotFoundException , CannotCompileException { CtClass cc = p o o l . g e t ( classname ) ; cc . s e t M o d i f i e r s ( M o d i f i e r . PUBLIC ) ; } } public i n t e r f a c e T r a n s l a t o r { public void s t a r t ( ClassPool p o o l ) throws NotFoundException , CannotCompileException ; public void onLoad ( ClassPool pool , S t r i n g classname ) throws NotFoundException , CannotCompileException ; } c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist ALADYN 62 Manipulation du code en Java Étude de cas : Javassist Rechargement d’une classe à l’exécution I import j a v a s s i s t . ∗ ; public class Main { public s t a t i c void main ( S t r i n g [ ] args ) throws Throwable { T r a n s l a t o r t = new M a k e P u b l i c T r a n s l a t o r ( ) ; ClassPool p o o l = ClassPool . g e t D e f a u l t ( ) ; Loader c l = new Loader ( ) ; c l . a d d T r a n s l a t o r ( pool , t ) ; c l . run ( " MyApp " , args ) ; } } Il existe deux manières de faire : 1 2 Utiliser le mécanisme de « hotswap » prévu par l’API JPDA. Utiliser l’API java.lang.instrumentation (que nous ne détaillerons pas ici). Pour l’utilisation du « hotswap » , une classe javassist.tools.Hotswapper est fournie par Javassist pour emballer de manière simple le rechargement. Exemple (venant des échantillons de programmes de Javassist) : Notez ici l’utilisation de deux chargeurs (le chargeur initial et celui donné par cl). L’existence de ces deux chargeurs peut faire qu’une même classe soit chargée dans les deux et alors considérées comme différentes. Dans ce cas, on peut devoir forcer le chargement de classes à se faire ensuite dans un chargeur spécifique pour préserver l’unicité de la classe chargée. ALADYN c 2004-2011 Jacques Malenfant � 61 Installation de la transformation c 2004-2011 Jacques Malenfant � Manipulation du code en Java Étude de cas : Javassist 63 c 2004-2011 Jacques Malenfant � ALADYN 64 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist Rechargement d’une classe à l’exécution II Rechargement d’une classe à l’exécution III import j a v a . i o . ∗ ; import j a v a s s i s t . u t i l . HotSwapper ; new H e l l o W o r l d ( ) . p r i n t ( ) ; n e w f i l e = new F i l e ( " H e l l o W o r l d . c l a s s " ) ; b y t e s = new byte [ ( i n t ) n e w f i l e . l e n g t h ( ) ] ; new F i l e I n p u t S t r e a m ( n e w f i l e ) . read ( b y t e s ) ; System . o u t . p r i n t l n ( " ∗∗ r e l o a d t h e o r i g i n a l v e r s i o n " ) ; public class Test { public s t a t i c void main ( S t r i n g [ ] args ) throws E x c e p ti o n { / / 8000 e s t l e numero de p o r t pour se c o n n e c t e r a l a JVM HotSwapper hs = new HotSwapper ( 8 0 0 0 ) ; new H e l l o W o r l d ( ) . p r i n t ( ) ; hs . r e l o a d ( " H e l l o W o r l d " , b y t e s ) ; new H e l l o W o r l d ( ) . p r i n t ( ) ; F i l e n e w f i l e = new F i l e ( " l o g g i n g / H e l l o W o r l d . c l a s s " ) ; byte [ ] b y t e s = new byte [ ( i n t ) n e w f i l e . l e n g t h ( ) ] ; new F i l e I n p u t S t r e a m ( n e w f i l e ) . read ( b y t e s ) ; System . o u t . p r i n t l n ( " ∗∗ r e l o a d a l o g g i n g v e r s i o n " ) ; } } Le lancement de java se fait alors par la commande (voir la documentation de Javassist pour plus de détails) : hs . r e l o a d ( " H e l l o W o r l d " , b y t e s ) ; c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN Manipulation du code en Java Étude de cas : Javassist 65 c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist Rechargement d’une classe à l’exécution IV ALADYN 66 Manipulation du code en Java Étude de cas : Javassist Introspection et intercession avec Javassist Les méthodes pour l’introspection sur les classes sont définies par CtClass d’une manière compatible avec l’API Reflection de Java. java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000 En plus de ces méthodes « standards », CtClass offrent également un ensemble de méthodes pour l’intercession : ajout de nouvelles méthodes, de nouveaux champs et de nouveaux constructeurs. Contrairement à la réflexion en Java, il est également possible en Javassist d’accéder et d’altérer le corps des méthodes existantes ou d’en définir de nouvelles. Javassist ne permet cependant pas de retirer des champs ou des méthodes, ni même de changer la signature des méthodes. c 2004-2011 Jacques Malenfant � ALADYN 67 c 2004-2011 Jacques Malenfant � ALADYN 68 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist Les méthodes Contraintes sur le code source Les méthodes sont représentées par des instances de CtMethod. Javassist inclut un compilateur Java pour traduire ce code source en code octal qui est ensuite introduit dans les méthodes. Les constructeurs sont représentés par des instances de CtConstructor. Le fait que le code source à ajouter doivent être introduit et compilé dans le contexte d’une méthode en code octal pose certaines contraintes. Ces classes définissent des méthodes insertBefore(), insertAfter(), and addCatch() pour ajouter du code source dans le corps des méthodes. La méthode insertAt() permet d’introduire du code à une ligne donnée de la méthode, à la condition que cette dernière ait été compilée avec l’option -g (« debugging » ou déverminage) laissant plus d’information dans le fichier .class sur les numéros de lignes et les noms de variables locales. c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist ALADYN En particulier, des noms de variables spéciaux sont définis, comme $0, $1, $2, pour représenter le pseudo-argument this et les arguments de la méthode, ou encore $sig pour accéder à un tableau de type Class des types des paramètres formels ou encore $type pour accéder à l’objet de type Class représentant le type du résultat formel. c 2004-2011 Jacques Malenfant � 69 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist Exemple I ALADYN 70 Manipulation du code en Java Étude de cas : Javassist Exemple II Soit la classe Point suivante : Pour donner l’équivalent de : class P o i n t { int x , y ; void move ( i n t dx , i n t dy ) { x += dx ; y += dy ; } } class P o i n t { int x , y ; void move ( i n t dx , i n t dy ) { { System . o u t . p r i n t l n ( dx ) ; System . o u t . p r i n t l n ( dy ) ; } x += dx ; y += dy ; } } On ajoute une trace de move par : ClassPool p o o l = ClassPool . g e t D e f a u l t ( ) ; CtClass cc = p o o l . g e t ( " P o i n t " ) ; CtMethod m = cc . getDeclaredMethod ( " move " ) ; m. i n s e r t B e f o r e ( " { System . o u t . p r i n t l n ( $1 ) ; System . o u t . p r i n t l n ( $2 ) ; } " ) ; cc . w r i t e F i l e ( ) ; c 2004-2011 Jacques Malenfant � ALADYN 71 c 2004-2011 Jacques Malenfant � ALADYN 72 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist Ajout d’une nouvelle méthode Méthodes mutuellement récursives I CtClass p o i n t = ClassPool . g e t D e f a u l t ( ) . g e t ( " P o i n t " ) ; CtMethod m = CtNewMethod . make ( " p u b l i c i n t xmove ( i n t dx ) { x += dx ; } " , point ) ; p o i n t . addMethod (m) ; c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN Javassist ne peut compiler une méthode appelant une autre méthode qui n’existe pas encore dans la classe. Pour les méthodes mutuellement récursives, il est possible de déconnecter la création de la signature de celle du corps de la méthode : 73 ALADYN 74 Manipulation du code en Java Étude de cas : Javassist Ajout d’un champ CtClass cc = . . . ; CtMethod m = CtNewMethod . make ( " p u b l i c a b s t r a c t i n t m( i n t i ) ; " , cc ) ; CtMethod n = CtNewMethod . make ( " p u b l i c a b s t r a c t i n t n ( i n t i ) ; " , cc ) ; cc . addMethod (m) ; cc . addMethod ( n ) ; m. setBody ( " { r e t u r n ( $1 <= 0 ) ? 1 : ( n ( $1 − 1 ) ∗ $1 ) ; } " ) ; n . setBody ( " { r e t u r n m( $1 ) ; } " ) ; cc . s e t M o d i f i e r s ( cc . g e t M o d i f i e r s ( ) & ~ M o d i f i e r . ABSTRACT ) ; ALADYN c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist Méthodes mutuellement récursives II c 2004-2011 Jacques Malenfant � Manipulation du code en Java Étude de cas : Javassist Préparation : CtClass p o i n t = ClassPool . g e t D e f a u l t ( ) . g e t ( " P o i n t " ) ; C t F i e l d f = new C t F i e l d ( CtClass . i n t T y p e , " z " , p o i n t ) ; puis, ajout sans initialisation : point . addField ( f ) ; ou encore avec initialisation : point . addField ( f , " 0 " ) ; 75 c 2004-2011 Jacques Malenfant � / / la valeur i n i t i a l e est 0. ALADYN 76 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist Modification du corps des méthodes I Modification du corps des méthodes II La méthode instrument de la classe CtMethod implante un patron visiteur qui va parcourir le code de la méthode et appliquer les éditions demandées par une instance d’ExprEditor qui lui est passée en paramètre. Les classes CtMethod et CtConstructor permettent de remplacer le corps de la méthode ou du constructeur représenté avec la méthode setBody. ExprEditor est une classe définissant une série de méthode edit qui sont chargées d’appliquer les modifications en fonction Pour des modifications plus complexes que de simples ajouts ou un remplacement total, Javassist fournit par la classe javassist.expr.ExprEditor et les autres classes du même paquetage le moyen de remplacer différentes expressions dans le corps d’une méthode. c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN Manipulation du code en Java Étude de cas : Javassist du type d’expressions rencontrées. Les types d’expressions identifiables sont les transtypages (« cast »), les appels de constructeurs, les accès à des champs, les clauses catch, les expressions instanceof, les appels de méthodes et les expressions de création d’objets et de tableaux («new »). c 2004-2011 Jacques Malenfant � 77 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Manipulation du code en Java Étude de cas : Javassist Exemple I ALADYN 78 Manipulation du code en Java Étude de cas : Javassist Exemple II Notez que le code de substitution passé à la méthode replace n’étant pas une expression mais un bloc. Il doit donc explicitement prévoir le résultat à retourner, ce qui est fait en affectation une valeur à la pseudo-variable $_. CtMethod cm = . . . ; cm . i n s t r u m e n t ( new E x p r E d i t o r ( ) { public void e d i t ( MethodCall m) throws CannotCompileException { i f (m. getClassName ( ) . equals ( " P o i n t " ) && m. getMethodName ( ) . equals ( " move " ) ) m. r e p l a c e ( " { $1 = 0 ; $_ = $proceed ( $$ ) ; } " ) ; } }); c 2004-2011 Jacques Malenfant � ALADYN La notation $proceed($$) exécute l’appel de méthode tel qu’il était avant la modification avec la même séquence de paramètres réels (désignée par la pseudo-variable $$. Voir la documentation Javassist pour les autres formes d’édition possibles et leurs pseudo-variables de contexte disponibles. 79 c 2004-2011 Jacques Malenfant � ALADYN 80 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réification des appels et interception Réflexion de comportement et modification du code Réification des appels et interception Réflexion de comportement et modification du code Progression historique 1 De la réflexion structurelle à la réflexion comportementale Java ne fut pas à l’origine un langage réflexif. 2 Réflexion structurelle et réification du code en Java 3 Réflexion de comportement en Java La reflexion de structure de l’API java.lang.reflect est apparue comme support à certaines opérations de haut niveau, comme l’envoi de message à distance (RMI). Son utilisation s’est ensuite propagée à de nombreuses API. Réification des appels et interception Le réflexion de comportement reste cependant largement en retrait, bien que de plus en plus d’API standards et d’outils apparaissent qui offre une certaine réflexion de comportement sans jamais cependant y faire explicitement référence. Réflexion de comportement et modification du code c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN 81 c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réification des appels et interception Réflexion de comportement et modification du code ALADYN 82 Réification des appels et interception Réflexion de comportement et modification du code Réflexion comportementale faible I 1 De la réflexion structurelle à la réflexion comportementale 2 Réflexion structurelle et réification du code en Java 3 Réflexion de comportement en Java L’objectif de la réflexion comportementale est de modifier la façon d’exécuter du code pour ajouter des choses à faire ou changer la façon de les faire. Une part significative des applications de la réflexion comportementale peuvent être réalisées simplement en ajoutant du code à exécuter avant et après l’exécution de la méthode. Réification des appels et interception C’est ce qu’on appelle les pré- et post-méthodes, c’est-à-dire des espèces de crochets parfois inclus directement dans le langage (en CLOS, le MOP en prévoit). Réflexion de comportement et modification du code On peut aussi obtenir de tels effets en donnant un mécanisme plus faible, dit d’interception, où : c 2004-2011 Jacques Malenfant � ALADYN 83 c 2004-2011 Jacques Malenfant � ALADYN 84 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réification des appels et interception Réflexion de comportement et modification du code Réflexion comportementale faible II Réification des appels et interception Réflexion de comportement et modification du code Interception des appels de méthodes Les méta-objets définissant lookup et apply fournissent un tel mécanisme, l’appel de lookup interceptant l’appel de méthodes et l’invocation de apply sur l’objet méthode réalisant l’invocation de la méthode du programme. on réifie l’appel de méthode en donnant un mécanisme aux programmeurs permettant d’intercepter les appels, eet on donne la possibilité d’activer la méthode elle-même, telle qu’elle apparaît dans le programme. Plusieurs langages de programmation offrent aussi de tels mécanismes d’interception, parfois « inconsciemment », comme la ruse du doesNotUnderstand en Smalltalk. En Java, les classes Proxy servent exactement à cela. c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN 85 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réification des appels et interception Réflexion de comportement et modification du code Les proxys en Java ALADYN 86 Réification des appels et interception Réflexion de comportement et modification du code La classe Proxy public class Proxy implements j a v a . i o . S e r i a l i z a b l e { ... public s t a t i c Class getProxyClass ( ClassLoader c l , Class < ? > . . . i n t e r f a c e s ) throws I l l e g a l A r g u m e n t E x c e p t i o n ; public s t a t i c O b j e c t newProxyInstance ( ClassLoader c l , Class [ ] i n t e r f a c e s , InvocationHandler h ) throws I l l e g a l A r g u m e n t E x c e p t i o n ; public s t a t i c boolean i s P r o x y C l a s s ( Class <?> c ) ; public s t a t i c I n v o c a t i o n H a n d l e r g e t I n v o c a t i o n H a n d l e r ( O b j e c t proxy ) throws I l l e g a l A r g u m e n t E x c e p t i o n ; } La classe java.lang.reflect.Proxy est en gros un générateur de classes permettant de créer des objets intercepteurs. Un objet intercepteur est un objet qui implante toutes les interfaces d’une classe C donnée, et qui va pouvoir intercepter tous les appels de méthodes sur une instance donnée de C. Lors de l’interception, l’objet intercepteur (proxy ) peut faire intervenir un objet de traitement de l’invocation (« invocation handler ») qui va dire quoi faire avec cet appel de méthode. L’interception fait intervenir l’objet méthode de la classe C qui est invoqué, et donc le traitement de l’invocation inclut la plupart du temps l’invocation de cet objet méthode pour l’exécuter. L’intercepteur peut aussi faire plein de choses pour entourer cette invocation. c 2004-2011 Jacques Malenfant � c 2004-2011 Jacques Malenfant � ALADYN 87 c 2004-2011 Jacques Malenfant � ALADYN 88 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réification des appels et interception Réflexion de comportement et modification du code L’interface InvocationHandler Réification des appels et interception Réflexion de comportement et modification du code Un proxy pour la trace des méthodes I L’interface InvocationHandler définit une méthode invoke qui intercepte tout appel de méthode faite à l’objet proxy : import j a v a . l a n g . r e f l e c t . ∗ ; import j a v a . i o . P r i n t W r i t e r ; public i n t e r f a c e I n v o c a t i o n H a n d l e r { public O b j e c t i n v o k e ( O b j e c t proxy , Method m, O b j e c t [ ] args ) throws Throwable ; } public class T r a c i n g I H implements I n v o c a t i o n H a n d l e r { public s t a t i c O b j e c t c r e a t e P r o x y ( O b j e c t o , P r i n t W r i t e r o u t ) { r e t u r n Proxy . newProxyInstance ( o . g e t C l a s s ( ) . getClassLoader ( ) , o . g e t C l a s s ( ) . g e t I n t e r f a c e s ( ) , new T r a c i n g I H ( o , o u t ) ) ; } protected O b j e c t t a r g e t ; protected P r i n t W r i t e r o u t ; Une méthode invoke qui ne fait rien s’écrit de la manière suivante : public O b j e c t i n v o k e ( O b j e c t proxy , Method m, O b j e c t [ ] args ) throws Throwable { r e t u r n m. i n v o k e ( t a r g e t , args ) ; } public T r a c i n g I H ( O b j e c t o , P r i n t W r i t e r o u t ) { this . t a r g e t = o ; this . out = out ; } où target référence l’objet réel pour lequel le proxy agit. c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN c 2004-2011 Jacques Malenfant � 89 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réification des appels et interception Réflexion de comportement et modification du code ALADYN 90 Réification des appels et interception Réflexion de comportement et modification du code Un proxy pour la trace des méthodes II public O b j e c t i n v o k e ( O b j e c t proxy , Method m, O b j e c t [ ] args ) throws Throwable { Object r e s u l t = null ; try { o u t . p r i n t l n (m. getName ( ) + " c a l l e d . " ) ; r e s u l t = m. i n v o k e ( t a r g e t , args ) ; } catch ( I n v o c a t i o n T a r g e t E x c e p t i o n e ) { o u t . p r i n t l n (m. getName ( ) + " throws " + e . getCause ( ) ) ; throw e . getCause ( ) ; } / / end t r y / c a t c h o u t . p r i n t l n (m. getName ( ) + " r e t u r n s . " ) ; return r e s u l t ; } 1 De la réflexion structurelle à la réflexion comportementale 2 Réflexion structurelle et réification du code en Java 3 Réflexion de comportement en Java Réification des appels et interception Réflexion de comportement et modification du code } c 2004-2011 Jacques Malenfant � ALADYN 91 c 2004-2011 Jacques Malenfant � ALADYN 92 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réification des appels et interception Réflexion de comportement et modification du code Réflexion de comportement et modification du code I Réification des appels et interception Réflexion de comportement et modification du code Réflexion de comportement et modification du code II Nous avons vu que la réflexion de comportement doit permettre de modifier le code de l’interprète, ce qui peut être réalisé en ajoutant du code à cet interprète via le programme. C’est ce que permet 3-Lisp, en interdisant cependant de modifier les fonctions existantes de l’interprète. Une forme limité de réflexion comportementale peut donc être obtenue s’il est possible de modifier dynamiquement le code du programme. C’est cette approche que prennent la plupart des gens qui cherchent à faire de la réflexion de comportement en Java. En Java, il est très difficile de modifier l’interprète, puisque c’est la machine virtuelle elle-même qui joue ce rôle. Il est souvent possible d’obtenir un effet similaire à celui des procédures réflexives en introduisant dans le code du programme le code réflexif qui aurait dû être exécuté par la procédure réflexive. c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN 93 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réification des appels et interception Réflexion de comportement et modification du code Exemple : introduction de métaobjets avec Javassist 94 Réification des appels et interception Réflexion de comportement et modification du code Classe originale : L’idée est de transformer le code d’une classe pour intercepter à la fois les appels de méthodes et les accès aux champs des objets d’une classe rendue réflexive. class Person { public i n t f ( i n t i ) { r e t u r n i + 1 ; } public i n t v a l u e ; } La classe Reflection est la classe principale implantant cette forme de réflexion. Classe après transformation : Les classes Loader et Compiler offrent des outils permettant de réaliser cette forme de réflexion au chargement ou alors par modification permanente des fichiers .class sur le système de fichiers. ALADYN ALADYN Transformation réalisée I Le « package » javassist.tools.reflect implante une forme de réflexion par interception utilisant le modèle des méta-objets décrit plus tôt. c 2004-2011 Jacques Malenfant � c 2004-2011 Jacques Malenfant � 95 c 2004-2011 Jacques Malenfant � ALADYN 96 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réification des appels et interception Réflexion de comportement et modification du code Transformation réalisée II Réification des appels et interception Réflexion de comportement et modification du code Transformation réalisée III class Person implements M e t a l e v e l { public i n t _ o r i g i n a l _ f ( i n t i ) { r e t u r n i + 1 ; } public i n t f ( i n t i ) { d e l e g a t e t o t h e m e ta o b j ec t } boolean m a k e R e f l e c t i v e ( j a v a . l a n g . Class c l a z z , j a v a . l a n g . Class metaobject , j a v a . l a n g . Class metaclass ) ; public i n t v a l u e ; public i n t _ r _ v a l u e ( ) { read " v a l u e " } public void _w_value ( i n t v ) { w r i t e " v a l u e " } boolean m a k e R e f l e c t i v e ( CtClass c l a z z , CtClass metaobject , CtClass metaclass ) ; boolean m a k e R e f l e c t i v e ( j a v a . l a n g . S t r i n g classname , j a v a . l a n g . S t r i n g metaobject , j a v a . l a n g . S t r i n g metaclass ) public C la ss Me t a o b j e c t _getClass ( ) { r e t u r n a class m e t a o b j e ct } public M e t a o b j e c t _ g e t M e t a o b j e c t ( ) { r e t u r n a m et a o b j ec t } public void _ s e t M e t a o b j e c t ( M e ta o b j e ct m) { change a m e ta o b j e c t } } Les paramètres de ces méthodes réprésentent : la classe à transformer la classe des méta-objets qui seront associés aux instances réflexives (doit être sous-classe de Metaobject Les classes Metaobject et ClassMetaobject sont des paramètres de la transformation implantée par les méthodes makeReflective de Reflection : c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN 97 c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réification des appels et interception Réflexion de comportement et modification du code Transformation réalisée IV ALADYN 98 Réification des appels et interception Réflexion de comportement et modification du code Transformation réalisée V la classe de l’objet représentant la classe des instances réflexives (doit être sous-classe de ClassMetaobject Les classes Metaobject et ClassMetaobject définissent le comportement minial des métaobjets et des classes des objets réflexifs respectivement. En particulier, ces classes définissent les méthodes _trapFieldRead, _trapFieldWrite et _trapMethodCall utilisées dans le protocole d’interception. Metaobject permet de créer des objets qui interceptent ces actions sur les objets alors que ClassMetaobject permet de créer des représentations des classes qui interceptent ces actions sur les champs et méthodes statiques de la classe. Voir la documentation de Javassist pour plus de détails. c 2004-2011 Jacques Malenfant � ALADYN Bien sûr, une application donne généralement ses propres classes de métaobjets et de classes d’objets réflexifs réalisant l’intention du programmeur dans son utilisation de cette forme de réflexion. 99 c 2004-2011 Jacques Malenfant � ALADYN 100 De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java Réification des appels et interception Réflexion de comportement et modification du code L’exemple du méta-objet verbeux... I Réification des appels et interception Réflexion de comportement et modification du code L’exemple du méta-objet verbeux... II package sample . r e f l e c t ; L’appel à la transformation : import j a v a s s i s t . t o o l s . r e f l e c t . ∗ ; public class Main { public s t a t i c void main ( S t r i n g [ ] args ) throws Throwable { Loader c l = ( Loader ) Main . class . getClassLoader ( ) ; c l . m a k e R e f l e c t i v e ( " sample . r e f l e c t . Person " , " sample . r e f l e c t . VerboseMetaobj " , " j a v a s s i s t . t o o l s . r e f l e c t . C la ss M et ao bj ec t " ) ; public class VerboseMetaobj extends M e ta o b j e ct { public VerboseMetaobj ( O b j e c t s e l f , O b j e c t [ ] args ) { super ( s e l f , args ) ; System . o u t . p r i n t l n ( " ∗∗ c o n s t r u c t e d : " + s e l f . g e t C l a s s ( ) . getName ( ) ) ; } c l . run ( " sample . r e f l e c t . Person " , args ) ; public O b j e c t t r a p F i e l d R e a d ( S t r i n g name ) { System . o u t . p r i n t l n ( " ∗∗ f i e l d read : " + name ) ; r e t u r n super . t r a p F i e l d R e a d ( name ) ; } } } et la classe des méta-objets verbeux : public void t r a p F i e l d W r i t e ( S t r i n g name , O b j e c t v a l u e ) { c 2004-2011 Jacques Malenfant � De la réflexion structurelle à la réflexion comportementale Réflexion structurelle et réification du code en Java Réflexion de comportement en Java ALADYN 101 Réification des appels et interception Réflexion de comportement et modification du code L’exemple du méta-objet verbeux... III System . o u t . p r i n t l n ( " ∗∗ f i e l d w r i t e : " + name ) ; super . t r a p F i e l d W r i t e ( name , v a l u e ) ; } public O b j e c t t r a p M e t h o d c a l l ( i n t i d e n t i f i e r , O b j e c t [ ] args ) throws Throwable { System . o u t . p r i n t l n ( " ∗∗ t r a p : " + getMethodName ( i d e n t i f i e r ) + " ( ) i n " + g e t C l a s s M e t a o b j e c t ( ) . getName ( ) ) ; r e t u r n super . t r a p M e t h o d c a l l ( i d e n t i f i e r , args ) ; } } c 2004-2011 Jacques Malenfant � ALADYN 103 c 2004-2011 Jacques Malenfant � ALADYN 102