Programmation Java/Réflexion
La réflexion ("reflection" en anglais) permet l'introspection des classes, c'est-à-dire de charger une classe, d'en créer une instance et d'accéder aux membres statiques ou non (appel de méthodes, lire et écrire les attributs) sans connaître la classe par avance.
Java possède une API permettant la réflexion.
Elle sert notamment à gérer des extensions ("plug-in" en anglais) pour une application.
L'API de réflexion.
Le package codice_1 possède trois classes utilisées pour l'utilisation dynamique des classes :
Les autres classes utiles sont définies dans le package codice_6 et permettent d'accéder aux détails d'une classe.
Les principales classes sont les suivantes :
Les classes représentant des membres d'une classe (codice_11, codice_12, codice_13) implémentent toutes l'interface codice_14 comportant les méthodes suivantes :
Charger une classe dynamiquement.
La classe codice_2 possède deux méthodes statiques pour obtenir une classe (après chargement si nécessaire) :
Il est également possible d'obtenir une codice_2 de manière statique :
Exemple :
package org.wikibooks.fr;
public class Exemple
 String nom;
 public String getNom()
Class c = Class.forName("org.wikibooks.fr.Exemple"); // sans référence statique à la classe
// ou
Class c = org.wikibooks.fr.Exemple.class; // référence statique à la classe
// ou
Class c = new Exemple().getClass(); // référence statique à la classe
Liste des membres d'une classe.
Les méthodes suivantes permettent de lister les membres d'une classe :
Les méthodes ci-dessus retournent les membres publics de la classe, comprenant également ceux hérités des classes mères.
Il existe une variante "Declared" de ces méthodes retournant tous les membres (publics, protégés, privés) déclarés par la classe uniquement (les membres hérités sont exclus).
Au lieu de lister tous les membres, puis en rechercher un en particulier, il est possible d'utiliser les méthodes spécifiques de recherche d'un membre précis d'une classe (publics et hérités, ou bien "Declared" pour tous ceux déclarés par la classe seule) :
Les membres retournés par toutes ces méthodes peuvent être d'instance ou statiques.
La méthode codice_41 retourne un tableau de codice_42 contenant toutes les annotations associées à la classe.
Instancier une classe et appel à un constructeur.
La méthode codice_43 de la classe codice_2 permet de créer une nouvelle instance de la classe, en appelant le constructeur sans paramètre de la classe (qui doit donc en posséder un) :
Class c = Class.forName("org.wikibooks.fr.Exemple");
Object o = c.newInstance(); // équivaut à new org.wikibooks.fr.Exemple();
Une classe comme celle ci-dessous peut ne pas avoir de constructeur sans paramètres :
package org.wikibooks.fr;
public class Livre
 String titre;
 int nb_pages;
 public Livre(String titre, int nb_pages)
 this.titre = titre;
 this.nb_pages = nb_pages;
Dans ce cas, il faut d'abord obtenir le constructeur, puis l'appeler :
Class c = Class.forName("org.wikibooks.fr.Livre"); // Accès à la classe Livre
Constructor constr = c.getConstructor(String.class, int.class); // Obtenir le constructeur (String, int)
Object o = constr.newInstance("Programmation Java", 120); // -> new Livre("Programmation Java", 120);
Pour les versions de Java antérieures à 5.0 où l'auto-boxing n'existe pas, et où il faut explicitement utiliser des tableaux :
Class c = Class.forName("org.wikibooks.fr.Livre"); // Accès à la classe Livre
Constructor constr = c.getConstructor(new Class[]{ String.class, Integer.TYPE }); // Obtenir le constructeur (String, int)
Object o = constr.newInstance(new Object{ "Programmation Java", Integer.valueOf(120) }); // -> new Livre("Programmation Java", 120);
Appel à une méthode.
L'appel à une méthode de la classe est basé sur le même principe que l'appel à un constructeur vu juste avant.
Cependant, pour obtenir la référence à une méthode, il faut spécifier le nom.
Lors de l'invocation de la méthode, il faut spécifier l'instance (l'objet) auquel s'applique la méthode (codice_45 pour une méthode statique).
Exemple :
package org.wikibooks.fr;
public class Livre
 String titre;
 int nb_pages;
 public Livre(String _titre, int _nb_pages) { 
 this.titre = _titre;
 this.nb_pages = _nb_pages;
 public int getNombreDeFeuilles(int pages_par_feuille)
 return (nb_pages+pages_par_feuille-1)/pages_par_feuille;
Class c = Class.forName("org.wikibooks.fr.Livre"); // Accès à la classe Livre
Constructor constr = c.getConstructor(String.class, int.class); // Obtenir le constructeur (String, int)
Object o = constr.newInstance("Programmation Java", 120); // -> new Livre("Programmation Java", 120);
Method method = c.getMethod("getNombreDeFeuilles", int.class); // Obtenir la méthode getNombreDeFeuilles(int)
int nb_feuilles = (int)method.invoke(o, 2); // -> o.getNombreDeFeuilles(2);
Accès à un attribut public.
L'accès à un attribut public se fait en appelant les méthodes sur l'instance de codice_9 obtenu auprès de la classe.
Exemple :
package org.wikibooks.fr;
public class Livre
 public String titre;
 public int nb_pages;
 public Livre(String _titre, int _nb_pages) { 
 this.titre = _titre;
 this.nb_pages = _nb_pages;
Class c = Class.forName("org.wikibooks.fr.Livre"); // Accès à la classe Livre
Constructor constr = c.getConstructor(String.class, int.class); // Obtenir le constructeur (String, int)
Object o = constr.newInstance("Programmation Java", 120); // -> new Livre("Programmation Java", 120);
Field f_titre = c.getField("titre"); // Obtenir l'attribut titre
String titre_du_livre = (String)f_titre.get(o); // -> o.titre
f_titre.set(o, "Java"); // -> o.titre = "Java";
Accès à un attribut privé.
Il est également possible d'avoir accès aux champs privés grâce à la méthode setAccessible de la classe Field.
Cela est cependant fortement déconseillé puisque modifier les valeurs d'un champs privée revient à violer le principe d'encapsulation.
Exemple :
package org.wikibooks.fr;
public class Livre
 private String titre;
 private int nb_pages;
 public Livre(String _titre, int _nb_pages) { 
 this.titre = _titre;
 this.nb_pages = _nb_pages;
Class c = Class.forName("org.wikibooks.fr.Livre"); // Accès à la classe Livre
Constructor constr = c.getConstructor(String.class, int.class); // Obtenir le constructeur (String, int)
Object o = constr.newInstance("Programmation Java", 120); // -> new Livre("Programmation Java", 120);
Field f_titre = c.getField("titre"); // Erreur: titre est privé
Field fields[] = c.getDeclaredFields();
fields[0].setAccessible(true); // titre désormais équivalent à un attribut publique
Field f_titre = fields[0]; // Obtenir l'attribut titre
String titre_du_livre = (String)f_titre.get(o); // -> o.titre
f_titre.set(o, "Java"); // -> o.titre = "Java";
Exemple concret : un gestionnaire d'extensions.
L'extension d'une application Java peut se faire en utilisant la réflexion pour charger dynamiquement une classe.
Le but de cet exemple est de permettre d'ajouter des fonctionnalités dynamiquement à une application, sans avoir à effectuer de changement autre que l'ajout d'une nouvelle archive JAR dans un répertoire.
Par défaut, une application est composée de plusieurs classes dont le chargement est effectué par la JVM.
Cependant, il faut que les chemins des répertoires de fichiers codice_47 ou des archives codice_48 soit renseignés d'avance avant le lancement de l'application dans le "classpath".
Chargeur de classes.
Supposons que l'application doive être capable de charger des extensions fournies sous forme de fichiers codice_48 situés dans un sous-répertoire nommé « "ext" ».
Quand l'application est lancée, le "classpath" est déjà configuré pour les classes de l'application, mais ne contient pas le répertoire "ext".
Mais comme les classes ne sont pas directement dans le répertoire, la JVM a besoin du chemin de chaque fichier codice_48 dans le "classpath".
L'application doit donc lister les fichiers codice_48 du répertoire "ext" pour construire un "classpath" passé au constructeur d'un nouveau chargeur de classes.
public static ClassLoader creerChargeurArchives(File dir) throws IOException
 if (!dir.isDirectory()) throw new FileNotFoundException("Répertoire non trouvé");
 File[] fichiers = dir.listFiles();
 ArrayList<URL> urls_fichiers = new ArrayList<>();
 // Ajouter les fichiers *.jar
 for(File f : fichiers)
 if (f.isFile() && f.getName().toLowerCase().endsWith(".jar"))
 urls_fichiers.add(f.toURI().toURL());
 // Créer le chargeur de classes pour les URLS de fichiers *.jar
 return new URLClassLoader(urls_fichiers.toArray(new URL[urls_fichiers.size()]));
Il est possible de créer un chargeur de classes par fichier codice_48.
Cependant, chaque chargeur de classe a un chargeur de classe parent, permettant aux classes chargées d'utiliser des classes gérées par le chargeur parent.
En l’occurrence il s'agit de celui des classes de l'application elle-même.
Cela signifie que les classes d'extensions peuvent utiliser les classes de l'application (classes de base, interfaces d'API, ...).
En utilisant un chargeur par fichier codice_48, chaque extension ne peut donc pas utiliser les classes d'une autre extension.
En utilisant un seul chargeur pour tous les fichiers codice_48, les extensions peuvent utiliser des classes d'autres extensions.
Cette dernière solution permet de créer des extensions utiles à d'autres extensions.
Toutefois l'inconvénient est que toutes les extensions sont chargées en mémoire même si elles ne sont pas utilisées.
De plus, le nom complet des classes doit être unique.
Chargement des classes d'extension.
Le chargement des classes d'extension doit utiliser le chargeur de classes des fichiers codice_48 trouvés dans le répertoire "ext".
Chaque extension doit utiliser un nom de paquetage unique afin que deux classes n'aient pas le même nom absolu.
Il faut aussi connaître la liste de noms des classes à charger.
Cette liste peut être fixe en calculant un nom de paquetage unique à partir du nom du fichier archive JAR (donc une seule classe chargée), ou elle peut être lue à partir d'un fichier inclut dans le fichier JAR (par exemple, un fichier nommé codice_56 à la racine du fichier JAR).
public static void chargerClasses(ClassLoader cl, File f_jar) throws IOException
 JarFile jf = new JarFile(f_jar);
 try
 JarEntry je = jf.getJarEntry("extension.ini");
 if (je != null)
 InputStream in = jf.getInputStream(je);
 try
 // Lecture du fichier texte
 BufferedReader br = new BufferedReader(new InputStreamReader(in));
 String line;
 while ((line = br.readLine()) != null)
 // Enlever l'éventuel commentaire de fin de ligne
 // commençant par le caractère #
 int i = line.indexOf('#');
 if (i>=0) line = line.substring(0,i);
 line = line.trim(); // Sans les espaces autour
 // Ne pas traiter les lignes vides
 if (line.length()==0) continue;
 // Traitement de la ligne
 // Il peut s'agir simplement de la classe à instancier immédiatement :
 // Class<? extends Object> c = cl.loadClass(line);
 // c.newInstance();
 // Ou d'une classe instanciée lorsqu'elle est utilisée dans un script ou
 // une commande de l'application en utilisant un nom associé à la classe :
 i = line.indexOf('='); // <nom> = <classe>
 if (i>=0)
 String nom = line.substring(0,i).trim();
 String nom_classe = line.substring(i+1).trim();
 // Associer le nom à la classe instanciée plus tard
 Class<? extends Object> c = cl.loadClass(nom_classe);
 map_extensions.put(nom, c);
public static void chargerExtensions(File dir) throws IOException
 // Créer le chargeur de classes
 ClassLoader cl = creerChargeurArchives(dir);
 File[] fichiers = dir.listFiles();
 // Charger les classes de chaque fichier *.jar
 for(File f : fichiers)
 if (f.isFile() && f.getName().toLowerCase().endsWith(".jar"))
 chargerClasses(cl, f);