IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

 

Développons en Java   2.30  
Copyright (C) 1999-2022 Jean-Michel DOUDOUX    (date de publication : 15/06/2022)

[ Précédent ] [ Sommaire ] [ Suivant ] [Télécharger ]      [Accueil ]

 

23. La gestion dynamique des objets et l'introspection

 

chapitre    2 3

 

Niveau : niveau 5 Confirmé 

 

Depuis la version 1.1 de Java, il est possible de créer et de gérer dynamiquement des objets.

L'introspection est un mécanisme qui permet de connaître le contenu d'une classe dynamiquement. Il permet notamment de savoir ce que contient une classe sans en avoir les sources. Ces mécanismes sont largement utilisés dans des outils de type IDE (Integrated Development Environnement : environnement de développement intégré).

Pour illustrer ces différents mécanismes, ce chapitre va construire une classe utilitaire qui proposera un ensemble de méthodes fournissant des informations sur une classe donnée.

Les différentes classes utiles pour l'introspection sont rassemblées dans le package java.lang.reflect.

Voici le début de cette classe qui attend dans son constructeur une chaîne de caractères précisant la classe sur laquelle elle va travailler.

Exemple ( code Java 1.1 ) :
import java.util.*;
import java.lang.reflect.*;

public class ClasseInspecteur {
  private Class classe;
  private String nomClasse;

  public ClasseInspecteur(String nomClasse) {
    this.nomClasse = nomClasse;
    try {
      classe = Class.forName(nomClasse);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Ce chapitre contient plusieurs sections :

 

23.1. La classe Class

Les instances de la classe Class sont des objets représentant les classes du langage. Il y aura une instance représentant chaque classe utilisée : par exemple la classe String, la classe Frame, la classe Class, etc ... Ces instances sont crées automatiquement par la machine virtuelle lors du chargement de la classe. Il est ainsi possible de connaître les caractéristiques d'une classe de façon dynamique en utilisant les méthodes de la classe Class. Les applications telles que les débogueurs, les inspecteurs d'objets et les environnements de développement doivent faire une analyse des objets qu'ils manipulent en utilisant ces mécanismes.

La classe Class est définie dans le package java.lang.

La classe Class permet :

  • de décrire une classe ou une interface par introspection : obtenir son nom, sa classe mère, la liste de ses méthodes, de ses variables de classe, de ses constructeurs et variables d'instances, etc ...
  • d'agir sur une classe en envoyant des messages à un objet Class comme à tout autre objet. Par exemple, créer dynamiquement à partir d'un objet Class une nouvelle instance de la classe représentée

 

23.1.1. L'obtention d'un objet de type Class

La classe Class ne possède pas de constructeur public mais il existe plusieurs façons d'obtenir un objet de la classe Class.

 

23.1.1.1. La détermination de la classe d'un objet

La méthode getClass() définit dans la classe Object renvoie une instance de la classe Class. Par héritage, tout objet Java dispose de cette méthode.

Exemple ( code Java 1.1 ) :
package introspection;

public class TestGetClass {

  public static void main(java.lang.String[] args) {
    String chaine = "test";
    Class classe = chaine.getClass();
    System.out.println("classe de l'objet chaine = "+classe.getName());
  }
}
Résultat :
classe de l'objet chaine = java.lang.String

 

23.1.1.2. L'obtention d'un objet Class à partir d'un nom de classe

La classe Class possède une méthode statique forName() qui permet à partir d'une chaîne de caractères désignant une classe d'instancier un objet de cette classe et de renvoyer un objet de la classe Class pour cette classe.

Cette méthode peut lever l'exception ClassNotFoundException.

Exemple ( code Java 1.1 ) :
public class TestForName {

  public static void main(java.lang.String[] args) {
    try {
      Class classe = Class.forName("java.lang.String");
      System.out.println("classe de l'objet chaine = "+classe.getName());
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
}
Résultat :
classe de l'objet chaîne = java.lang.String

 

23.1.1.3. Une troisième façon d'obtenir un objet Class

Il est possible d'avoir un objet de la classe Class en écrivant type.class où type est le nom d'une classe.

Exemple ( code Java 1.1 ) :
package introspection;

public class TestClass {

  public static void main(java.lang.String[] args) {
    Class c = Object.class;
    System.out.println("classe de Object  = "+c.getName());
  }
}
Résultat :
classe de Object  = java.lang.Object

 

23.1.2. Les méthodes de la classe Class

La classe Class fournit de nombreuses méthodes pour obtenir des informations sur la classe qu'elle représente. Voici les principales méthodes :

Méthodes Rôle
static Class forName(String) Instancier un objet de la classe dont le nom est fourni en paramètre et renvoie un objet Class la représentant
Class[] getClasses() Renvoyer les classes et interfaces publiques qui sont membres de la classe
Constructor[] getConstructors() Renvoyer les constructeurs publics de la classe
Class[] getDeclaredClasses() Renvoyer un tableau des classes définies comme membres dans la classe
Constructor[] getDeclaredConstructors() Renvoyer tous les constructeurs de la classe
Field[] getDeclaredFields() Renvoyer un tableau de tous les attributs définis dans la classe
Method[] getDeclaredMethods() Renvoyer un tableau de toutes les méthodes
Field[] getFields() Renvoyer un tableau des attributs publics
Class[] getInterfaces() Renvoyer un tableau des interfaces implémentées par la classe
Method[] getMethod() Renvoyer un tableau des méthodes publiques de la classe incluant celles héritées
int getModifiers() Renvoyer un entier qu'il faut décoder pour connaître les modificateurs de la classe
Package getPackage() Renvoyer le package de la classe
Classe getSuperClass() Renvoyer la classe mère de la classe
boolean isArray() Indiquer si la classe est un tableau
boolean IsInterface() Indiquer si la classe est une interface
Object newInstance() Créer une nouvelle instance de la classe

 

23.2. La recherche des informations sur une classe

En utilisant les méthodes de la classe Class, il est possible d'obtenir quasiment toutes les informations sur une classe.

23.2.1. La recherche de la classe mère d'une classe

La classe Class possède une méthode getSuperClass() qui retourne un objet de la classe Class représentant la classe mère si elle existe sinon elle retourne null.

Pour obtenir toute la hiérarchie d'une classe il suffit d'appeler successivement cette méthode sur l'objet qu'elle a retourné.

Exemple ( code Java 1.1 ) : méthode qui retourne un vecteur contenant les classes mères
  public Vector getClassesParentes() {

    Vector cp = new Vector();

    Class sousClasse = classe;
    Class superClasse;

    cp.add(sousClasse.getName());
    superClasse = sousClasse.getSuperclass();
    while (superClasse != null) {
      cp.add(0,superClasse.getName());
      sousClasse = superClasse;
      superClasse = sousClasse.getSuperclass();
    }
    return cp;
  }

 

23.2.2. La recherche des modificateurs d'une classe

La classe Class possède une méthode getModifiers() qui retourne un entier représentant les modificateurs de la classe. Pour décoder cette valeur, la classe Modifier possède plusieurs méthodes qui attendent cet entier en paramètre et qui retournent un booléen selon leur fonction : isPublic(), isAbstract(), isFinal(), ...

La classe Modifier ne contient que des constantes et des méthodes statiques qui permettent de déterminer les modificateurs d'accès :

Méthode Rôle
boolean isAbstract(int) Renvoyer true si le paramètre contient le modificateur abstract
boolean isFinal(int) Renvoyer true si le paramètre contient le modificateur final
boolean isInterface(int) Renvoyer true si le paramètre contient le modificateur interface
boolean isNative(int) Renvoyer true si le paramètre contient le modificateur native
boolean isPrivate(int) Renvoyer true si le paramètre contient le modificateur private
boolean isProtected(int) Renvoyer true si le paramètre contient le modificateur protected
boolean isPublic(int) Renvoyer true si le paramètre contient le modificateur public
boolean isStatic(int) Renvoyer true si le paramètre contient le modificateur static
boolean isSynchronized(int) Renvoyer true si le paramètre contient le modificateur synchronized
boolean isTransient(int) Renvoyer true si le paramètre contient le modificateur transient
boolean isVolatile(int) Renvoyer true si le paramètre contient le modificateur volatile

Ces méthodes étant static il est inutile d'instancier un objet de type Modifier pour les utiliser.

Exemple ( code Java 1.1 ) :
  public Vector getModificateurs() { 
    Vector cp = new Vector(); 
    
    int m = classe.getModifiers(); 
    if (Modifier.isPublic(m)) 
      cp.add("public"); 
    if (Modifier.isAbstract(m)) 
      cp.add("abstract"); 
    if (Modifier.isFinal(m)) 
      cp.add("final"); 
    return cp; 
  }

 

23.2.3. La recherche des interfaces implémentées par une classe

La classe Class possède une méthode getInterfaces() qui retourne un tableau d'objets de type Class contenant les interfaces implémentées par la classe.

Exemple ( code Java 1.1 ) :
  public Vector getInterfaces() {  
    Vector cp = new Vector(); 
     
    Class[] interfaces = classe.getInterfaces(); 
    for (int i = 0; i < interfaces.length; i++) { 
      cp.add(interfaces[i].getName()); 
    } 
    return cp; 
  }

 

23.2.4. La recherche des champs publics

La classe Class possède une méthode getFields() qui retourne les attributs public de la classe. Cette méthode retourne un tableau d'objets de type Field.

La classe Class possède aussi une méthode getField() qui attend en paramètre un nom d'attribut et retourne un objet de type Field si celui-ci est défini dans la classe ou dans une de ses classes mères. Si la classe ne contient pas d'attribut dont le nom correspond au paramètre fourni, la méthode getField() lève une exception de la classe NoSuchFieldException.

La classe Field représente un attribut d'une classe ou d'une interface et permet d'obtenir des informations sur cet attribut. Elle possède plusieurs méthodes :

Méthode Rôle
String getName() Retourner le nom de l'attribut
Class getType() Retourner un objet de type Class qui représente le type de l'attribut
Class getDeclaringClass() Retourner un objet de type Class qui représente la classe qui définit l'attribut
int getModifiers() Retourner un entier qui décrit les modificateurs d'accès. Pour les connaître précisément il faut utiliser les méthodes static de la classe Modifier.
Object get(Object) Retourner la valeur de l'attribut pour l'instance de l'objet fournie en paramètre. Il existe aussi plusieurs méthodes getXXX() où XXX représente un type primitif et qui renvoient la valeur dans ce type.

Exemple ( code Java 1.1 ) :
  public Vector getChampsPublics() { 
    Vector cp = new Vector(); 

    Field[] champs = classe.getFields(); 
    for (int i = 0; i < champs.length; i++) 
      cp.add(champs[i].getType().getName()+" "+champs[i].getName()); 
    return cp; 
  }

 

23.2.5. La recherche des paramètres d'une méthode ou d'un constructeur

L'exemple ci-dessous présente une méthode qui permet de formater sous forme de chaîne de caractères les paramètres d'une méthode fournis sous la forme d'un tableau d'objets de type Class.

Exemple ( code Java 1.1 ) :
  private String rechercheParametres(Class[] classes) { 
    StringBuffer param = new StringBuffer("("); 
                 
    for (int i = 0; i < classes.length; i ++) { 
      param.append(formatParametre(classes[i].getName())); 
      if (i < classes.length - 1)  
        param.append(", "); 
    } 
    param.append(")"); 
     
    return param.toString(); 
  }

La méthode getName() de la classe Class renvoie une chaîne de caractères formatée qui précise le type de la classe. Ce type est représenté par une chaîne de caractères qu'il faut décoder pour l'extraire.

Si le type de la classe est un tableau alors la chaîne commence par un nombre de caractères '[' correspondant à la dimension du tableau.

Ensuite la chaîne contient un caractère qui précise un type primitif ou un objet. Dans le cas d'un objet, le nom de la classe de l'objet avec son package complet est contenu dans la chaîne suivie d'un caractère ';'.

Caractère

Type

B byte
C char
D double
F float
I int
J long
Lclassname; classe ou interface
S short
Z boolean

Exemple :

La méthode getName() de la classe Class représentant un objet de type float[10][5] renvoie « [[F »

Pour simplifier les traitements, la méthode formatParametre() ci-dessous retourne une chaîne de caractères qui décode le contenu de la chaîne retournée par la méthode getName() de la classe Class.

Exemple :
  private String formatParametre(String s) { 
   
    if (s.charAt(0) == '[') { 
     
      StringBuffer param = new StringBuffer(""); 
      int dimension = 0; 
      while (s.charAt(dimension) == '[') dimension++; 
       
      switch(s.charAt(dimension)) { 
        case 'B' : param.append("byte");break; 
        case 'C' : param.append("char");break; 
        case 'D' : param.append("double");break; 
        case 'F' : param.append("float");break; 
        case 'I' : param.append("int");break; 
        case 'J' : param.append("long");break; 
        case 'S' : param.append("short");break; 
        case 'Z' : param.append("boolean");break; 
        case 'L' : param.append(s.substring(dimension+1,s.indexOf(";"))); 
      } 
       
      for (int i =0; i < dimension; i++) 
        param.append("[]"); 
                 
      return param.toString();  
    }           
    else return s; 
                 
  }

 

23.2.6. La recherche des constructeurs de la classe

La classe Class possède une méthode getConstructors() qui retourne un tableau d'objets de type Constructor contenant les constructeurs de la classe.

La classe Constructor représente un constructeur d'une classe et possède plusieurs méthodes :

Méthode Rôle
String getName() Retourner le nom du constructeur
Class[] getExceptionTypes() Retourner un tableau de type Class qui représente les exceptions qui peuvent être propagées par le constructeur
Class[] getParametersType() Retourner un tableau de type Class qui représente les paramètres du constructeur
int getModifiers() Retourner un entier qui décrit les modificateurs d'accès. Pour les connaître précisément il faut utiliser les méthodes static de la classe Modifier.
Object newInstance(Object[]) Instancier un objet en utilisant le constructeur avec les paramètres fournis à la méthode

Exemple ( code Java 1.1 ) :
  public Vector getConstructeurs() { 
     
    Vector cp = new Vector(); 
    Constructor[] constructeurs = classe.getConstructors(); 
    for (int i = 0; i < constructeurs.length; i++) { 
      cp.add(rechercheParametres(constructeurs[i].getParameterTypes())); 
    }
             
    return cp; 
  }

L'exemple ci-dessus utilise la méthode rechercherParamètres() définie précédemment pour simplifier les traitements.

23.2.7. La recherche des méthodes publiques

Pour consulter les méthodes d'un objet, il faut obtenir sa classe et lui envoyer le message getMethod(), qui renvoie les méthodes publiques qui sont déclarées dans la classe ou qui sont héritées des classes mères.

Elle renvoie un tableau d'instances de la classe Method du package java.lang.reflect.

Une méthode est caractérisée par un nom, une valeur de retour, une liste de paramètres, une liste d'exceptions et une classe d'appartenance.

La classe Method contient plusieurs méthodes :

Méthode Rôle
Class[] getParameterTypes Renvoyer un tableau de classes représentant les paramètres.
Class getReturnType Renvoyer le type de la valeur de retour de la méthode.
String getName() Renvoyer le nom de la méthode
int getModifiers() Renvoyer un entier qui représente les modificateurs d'accès
Class[] getExceptionTypes Renvoyer un tableau de classes contenant les exceptions propagées par la méthode
Class getDeclaringClass[] Renvoyer la classe qui définit la méthode

Exemple ( code Java 1.1 ) :
  public Vector getMethodesPubliques() {  
    Vector cp = new Vector(); 
    
    Method[] methodes = classe.getMethods(); 
    for (int i = 0; i < methodes.length; i++) { 
      StringBuffer methode = new StringBuffer(); 
                 
      methode.append(formatParametre(methodes[i].getReturnType().getName())); 
      methode.append(" "); 
      methode.append(methodes[i].getName()); 
      methode.append(rechercheParametres(methodes[i].getParameterTypes())); 
       
      cp.add(methode.toString()); 
    } 
    return cp; 
  }

L'exemple ci-dessus utilise les méthodes formatParametre() et rechercherParametres() définies précédemment pour simplifier les traitements.

 

23.2.8. La recherche de toutes les méthodes

Pour consulter toutes les méthodes d'un objet, il faut obtenir sa classe et lui envoyer le message getDeclaredMethods(), qui renvoie toutes les méthodes qui sont déclarées dans la classe ou qui sont héritées des classes mères quelque soit leur accessibilité.

Elle renvoie un tableau d'instances de la classe Method du package java.lang.reflect.

Exemple :
  public List getSignatureMethodes() { 
     
    List cp = new ArrayList(); 
    Method[] methodes = classe.getDeclaredMethods(); 
    for (int i = 0; i < methodes.length; i++) { 
      StringBuffer methode = new StringBuffer(); 
                 
      methode.append(formatParametre(methodes[i].getReturnType().getName())); 
      methode.append(" "); 
      methode.append(methodes[i].getName()); 
      methode.append(rechercheParametres(methodes[i].getParameterTypes())); 
       
      cp.add(methode.toString()); 
    } 
    return cp; 
  }

L'exemple ci-dessus utilise les méthodes formatParametre() et rechercherParametres() définies précédemment pour simplifier les traitements.

 

23.2.9. La recherche des getters et des setters

Par convention, la valeur d'une propriété est gérée grâce à deux méthodes public :

  • un getter : c'est une méthode qui permet d'obtenir la valeur de la propriété. Son nom commence par « get » suivi du nom de la propriété. Elle ne doit pas avoir de paramètre et renvoie la valeur de la propriété
  • un setter : c'est une méthode pour modifier la valeur d'une propriété. Son nom commence par « set » suivi du nom de la propriété. Elle n'attend qu'un seul paramètre qui est la nouvelle valeur de la propriété et ne renvoie rien (void)

Même si elle ne le propose pas directement, il est possible d'utiliser l'API Reflection pour obtenir les getters et les setters d'une classe.

Exemple :
package fr.jmdoudoux.dej.reflection;
      
import java.beans.BeanInfo;
import java.lang.reflect.Method;

public class TestGetterSetters {

  public static void main(String args[]) {
    afficherGettersSetters(MaClasse.class);
  }

  public static void afficherGettersSetters(Class aClass) {
    Method[] methods = aClass.getMethods();
    System.out.println("classe : " + aClass.getName());
    for (Method method : methods) {
      if (isGetter(method)) {
        System.out.println("  getter : " + method);
      }
      if (isSetter(method)) {
        System.out.println("  setter : " + method);
      }
    }
  }

  public static boolean isGetter(Method method) {
    boolean result = method.getName().startsWith("get")
            && (method.getParameterTypes().length == 0)
            && (!Void.class.equals(method.getReturnType()));
    return result;
  }

  public static boolean isSetter(Method method) {
    boolean result = (method.getName().startsWith("set"))
            && (method.getParameterTypes().length == 1);
    return result;
  }
}

 

23.3. La définition dynamique d'objets

L'API Reflection permet de créer dynamiquement des instances d'un type.

 

23.3.1. La création d'objets grâce à la classe Class

La méthode statique forName() de la classe Class permet de charger dynamiquement une classe dont le nom pleinement qualifié est fourni en paramètre. Elle renvoie une instance de la classe Class qui encapsule la classe chargée.

La méthode newInstance() de la classe Class permet de créer une instance de la classe et d'invoquer son constructeur par défaut.

Exemple ( code Java 1.4 ) :
import java.util.logging.Level;
import java.util.logging.Logger;

import fr.jmdoudoux.dej.introspection.MaClasse;

public class TestNewInstance {
  public static Logger LOGGER     = Logger.getLogger("TestNewInstance");
  public static String NOM_CLASSE = "fr.jmdoudoux.dej.introspection.MaClasse";

  public static void main(String[] args) {
    try {
      Class classe = Class.forName(NOM_CLASSE);
      MaClasse instance = (MaClasse) classe.newInstance();
      instance.afficher();
    } catch (ClassNotFoundException cnfe) {
      if (LOGGER.isLoggable(Level.SEVERE))
        LOGGER.log(Level.SEVERE, "La classe " + NOM_CLASSE + " n'existe pas",
            cnfe);
    } catch (InstantiationException ie) {
      if (LOGGER.isLoggable(Level.SEVERE))
        LOGGER.log(Level.SEVERE, "La classe " + NOM_CLASSE
            + " n'est pas instanciable", ie);
    } catch (IllegalAccessException iae) {
      if (LOGGER.isLoggable(Level.SEVERE))
        LOGGER.log(Level.SEVERE, "La classe " + NOM_CLASSE
            + " n'est pas accessible", iae);
    }
  }
}

A partir de Java 5, la classe Class est générique.

Exemple ( code Java 5.0 ) :
import java.util.logging.Level;
import java.util.logging.Logger;

import fr.jmdoudoux.dej.introspection.MaClasse;

public class TestNewInstance {
  public static Logger LOGGER     = Logger.getLogger("TestNewInstance");
  public static String NOM_CLASSE = "fr.jmdoudoux.dej.introspection.MaClasse";

  public static void main(String[] args) {
    try {
      Class<MaClasse> classe = (Class<MaClasse>) Class.forName(NOM_CLASSE);
      MaClasse instance = classe.newInstance();
      instance.afficher();
    } catch (ClassNotFoundException cnfe) {
      if (LOGGER.isLoggable(Level.SEVERE))
        LOGGER.log(Level.SEVERE, "La classe " + NOM_CLASSE + " n'existe pas", cnfe);
    } catch (InstantiationException ie) {
      if (LOGGER.isLoggable(Level.SEVERE))
        LOGGER.log(Level.SEVERE, "La classe " + NOM_CLASSE
            + " n'est pas instanciable", ie);
    } catch (IllegalAccessException iae) {
      if (LOGGER.isLoggable(Level.SEVERE))
        LOGGER.log(Level.SEVERE, "La classe " + NOM_CLASSE
            + " n'est pas accessible", iae);
    }
  }
}

La méthode newInstance() de la classe Class présente plusieurs contraintes :

  • seul le constructeur sans paramètre peut être invoqué
  • ce constructeur doit être public
  • toutes les exceptions checked et unchecked levées lors de l'invocation du constructeur sont propagées

 

23.3.2. La création d'objets grâce à la classe Constructor

A partir de la version 1.1, le package java.lang.reflect propose la classe Constructor pour créer des instances en invoquant un constructeur quelconque d'une classe.

La méthode getDeclaredConstructor() de la classe Class permet d'obtenir une instance de la classe Constructor qui encapsule le constructeur dont les types des paramètres ont été fournis à la méthode getDeclaredConstructor().

La méthode getDeclaredMethod() attend en paramètre un tableau d'objets de type Class qui doit contenir les types de chaque paramètre dans l'ordre de leur définition dans la signature du constructeur souhaité.

La classe Constructor propose la méthode newInstance() qui attend en paramètre un tableau de type Object devant contenir les valeurs qui seront fournies lors de l'invocation du constructeur.

Exemple :
import java.lang.reflect.Constructor;
import java.util.logging.Level;
import java.util.logging.Logger;

import fr.jmdoudoux.dej.introspection.MaClasse;

public class TestGetConstrutor {

  public static Logger LOGGER     = Logger.getLogger("TestGetConstrutor");
  public static String NOM_CLASSE = "fr.jmdoudoux.dej.introspection.MaClasse";

  public static void main(String[] args) {
    try {
      Class classe = Class.forName(NOM_CLASSE);
      Constructor constructeur = classe.getConstructor(new Class[] {
          boolean.class, Class.forName("java.lang.String") });
      MaClasse instance = (MaClasse) constructeur.newInstance(new Object[] {
          Boolean.FALSE, "nom instance" });
      instance.afficher();
    } catch (ClassNotFoundException cnfe) {
      if (LOGGER.isLoggable(Level.SEVERE))
        LOGGER.log(Level.SEVERE, "La classe " + NOM_CLASSE + " n'existe pas",
            cnfe);
    } catch (NoSuchMethodException nme) {
      if (LOGGER.isLoggable(Level.SEVERE))
        LOGGER.log(Level.SEVERE, "Le constructeur de la classe " + NOM_CLASSE
            + " n'existe pas", nme);
    } catch (InstantiationException ie) {
      if (LOGGER.isLoggable(Level.SEVERE))
        LOGGER.log(Level.SEVERE, "La classe " + NOM_CLASSE
            + " n'est pas instanciable", ie);
    } catch (IllegalAccessException iae) {
      if (LOGGER.isLoggable(Level.SEVERE))
        LOGGER.log(Level.SEVERE, "La classe " + NOM_CLASSE
            + " n'est pas accessible", iae);
    } catch (java.lang.reflect.InvocationTargetException ite) {
      if (LOGGER.isLoggable(Level.SEVERE))
        LOGGER.log(Level.SEVERE, "Le constructueur de la classe " + NOM_CLASSE
            + " a leve une exception", ite);
    } catch (IllegalArgumentException iae) {
      if (LOGGER.isLoggable(Level.SEVERE))
        LOGGER.log(Level.SEVERE, "Un parametre du constructueur de la classe "
            + NOM_CLASSE + " n'est pas du bon type", iae);
    }
  }
}

A partir de Java 5, les classes Class et Constructor sont génériques.

Exemple ( code Java 5.0 ) :
import java.lang.reflect.Constructor;
import java.util.logging.Level;
import java.util.logging.Logger;
import fr.jmdoudoux.dej.introspection.MaClasse;

public class TestGetConstrutor {

  public static Logger LOGGER     = Logger.getLogger("TestGetConstrutor");
  public static String NOM_CLASSE = "fr.jmdoudoux.dej.introspection.MaClasse";

  public static void main(String[] args) {
    try {
      Class<MaClasse> classe = (Class<MaClasse>) Class.forName(NOM_CLASSE);
      Constructor<MaClasse> constructeur = classe.getConstructor(new Class[] {
          boolean.class, Class.forName("java.lang.String") });
      MaClasse instance = constructeur.newInstance(new Object[] {
          Boolean.FALSE, "nom instance" });
      instance.afficher();
    } catch (ClassNotFoundException cnfe) {
      if (LOGGER.isLoggable(Level.SEVERE))
        LOGGER.log(Level.SEVERE, "La classe " + NOM_CLASSE + " n'existe pas",
            cnfe);
    } catch (NoSuchMethodException nme) {
      if (LOGGER.isLoggable(Level.SEVERE))
        LOGGER.log(Level.SEVERE, "Le constructeur de la classe " + NOM_CLASSE
            + " n'existe pas", nme);
    } catch (InstantiationException ie) {
      if (LOGGER.isLoggable(Level.SEVERE))
        LOGGER.log(Level.SEVERE, "La classe " + NOM_CLASSE
            + " n'est pas instanciable", ie);
    } catch (IllegalAccessException iae) {
      if (LOGGER.isLoggable(Level.SEVERE))
        LOGGER.log(Level.SEVERE, "La classe " + NOM_CLASSE
            + " n'est pas accessible", iae);
    } catch (java.lang.reflect.InvocationTargetException ite) {
      if (LOGGER.isLoggable(Level.SEVERE))
        LOGGER.log(Level.SEVERE, "Le constructueur de la classe " + NOM_CLASSE
            + " a leve une exception", ite);
    } catch (IllegalArgumentException iae) {
      if (LOGGER.isLoggable(Level.SEVERE))
        LOGGER.log(Level.SEVERE, "Un parametre du constructueur de la classe "
            + NOM_CLASSE + " n'est pas du bon type", iae);
    }
  }
}

Si une exception est levée lors de l'invocation du constructeur, celle-ci est chaînée dans une exception checked de type TargetInvocationException.

 

23.4. L'invocation dynamique d'une méthode

L'API Reflection permet d'invoquer dynamiquement une méthode d'un objet.

Pour invoquer dynamiquement une méthode d'une instance, il faut utiliser la méthode invoke(Object obj, Object[] args) de la classe java.lang.Method qui possède plusieurs paramètres :

  • le premier paramètre est l'instance sur laquelle la méthode doit être invoquée
  • les paramètres suivants sont les valeurs qui seront passées en paramètres lors de l'invocation : un nombre arbitraire de paramètres peuvent être passés. Les valeurs des paramètres fournies doivent respecter le type et l'ordre de la signature de la méthode.
Exemple :
package fr.jmdoudoux.dej.reflection;
      
public class MaClasse {

  public void maMethode() {
    System.out.println("maMethode sans param");
  }

  public String maMethode(String param1) {
    System.out.println("maMethode avec String:"+param1);
    return param1;
  }

  public String maMethode(String param1, int param2) {
    String resultat = param1+param2;
    System.out.println("maMethode avec String:"+param1+", int:"+param2);
    return resultat;
  }

  public String maMethode(String param1, Integer param2) {
    String resultat = param1+param2;
    System.out.println("maMethode avec String:"+param1+", int:"+param2);
    return resultat;
  }
    
  public void maMethode(int param1) {
    System.out.println("maMethode avec int:"+param1);
  }
  
  private void maMethodePrivee() {
    System.out.println("maMethodePrivee sans param");
  }

  public static void maMethodeStatic() {
    System.out.println("maMethodeStatic sans param");
  }
}
Exemple :
package fr.jmdoudoux.dej.reflection;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class TestExecuterMethode {

  public static void main(String[] args) {
    MaClasse maClasse = new MaClasse();
    try {
      Object retour = executerMethode(maClasse, "maMethode", null);
      System.out.println("Valeur de retour = " + retour);
      retour = executerMethode(maClasse, "maMethode", new
        Object[]{"chaine1"});
      System.out.println("Valeur de retour = " + retour);
      retour = executerMethode(maClasse, "maMethode", new
        Object[]{"chaine", 99});
      System.out.println("Valeur de retour = " + retour);
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  public static Object executerMethode(Object objet, String nomMethode, 
    Object[] parametres) throws Exception {
    Object retour;
    Class[] typeParametres = null;
    
    if (parametres != null) {
      typeParametres = new Class[parametres.length];
      for (int i = 0; i < parametres.length; ++i) {
        typeParametres[i] = parametres[i].getClass();
      }
    }
    
    Method m = objet.getClass().getMethod(nomMethode, typeParametres);
    if (Modifier.isStatic(m.getModifiers())) {
      retour = m.invoke(null, parametres);
    } else {
      retour = m.invoke(objet, parametres);
    }
    return retour;
  }
}
Résultat :
maMethode sans param
Valeur de retour = null
maMethode avec String:chaine1
Valeur de retour = chaine1
maMethode avec String:chaine, int:99
Valeur de retour = chaine99

 

23.4.1. La passage de paramètre à la méthode invoquée

Une exception de type IllegalArgumentException est levée si aucune méthode dont la signature correspond aux types passés en paramètre n'est trouvée.

Exemple :
package fr.jmdoudoux.dej.reflection;
      
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestExecuterMethode {

  public static void main(String[] args) {
    MaClasse maClasse = new MaClasse();
    try {
      Class<?> c = maClasse.getClass();
      Method m = c.getMethod("maMethode");
      System.out.format("Methode : %s%n", m.toGenericString());
      m.invoke(maClasse, "test");        
    } catch (NoSuchMethodEx ception x){
      x.printStackTrace();
    } catch (IllegalArgumentException ex) {
      ex.printStackTrace();
    } catch (IllegalAccessException ex) {
      ex.printStackTrace();
    } catch (InvocationTargetException ex) {
      ex.printStackTrace();
    }
  }
}
Résultat :
Methode : public void fr.jmdoudoux.dej.reflection.MaClasse.maMethode()
java.lang.IllegalArgumentException: wrong number of arguments
       at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
       at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
       at java.lang.reflect.Method.invoke(Method.java:601)
       at fr.jmdoudoux.dej.reflection.TestExecuterMethode.main(TestExecuterMethode.java:14)

Comme le second paramètre de la méthode invoke() est un varargs, il est possible de passer un tableau de type Object de taille 0 pour indiquer qu'il n'y a pas de paramètre à la méthode.

Exemple :
package fr.jmdoudoux.dej.reflection;
      
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestExecuterMethode {

  public static void main(String[] args) {
    MaClasse maClasse = new MaClasse();
    try {
      Class<?> c = maClasse.getClass();
      Method m = c.getMethod("maMethode");
      System.out.format("Methode : %s%n", m.toGenericString());
      m.invoke(maClasse, new Object[0]);        
    } catch (NoSuchMethodException x) {
      x.printStackTrace();
    } catch (IllegalArgumentException ex) {
      ex.printStackTrace();
    } catch (IllegalAccessException ex) {
      ex.printStackTrace();
    } catch (InvocationTargetException ex) {
      ex.printStackTrace();
    }
  }
}
Résultat :
Methode : public void fr.jmdoudoux.dej.reflection.MaClasse.maMethode()
maMethode sans param

Si la valeur null est passée comme paramètre de la méthode invoke() pour invoquer une méthode sans paramètre alors le compilateur émet un warning.

Exemple :
package fr.jmdoudoux.dej.reflection;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestExecuterMethode {

  public static void main(String[] args) {
    MaClasse maClasse = new MaClasse();
    try {
      Class<?> c = maClasse.getClass();
      Method m = c.getMethod("maMethode");
      System.out.format("Methode : %s%n", m.toGenericString());
        m.invoke(maClasse, null);        
    } catch (NoSuchMethodException x) {
      x.printStackTrace();
    } catch (IllegalArgumentException ex) {
      ex.printStackTrace();
    } catch (IllegalAccessException ex) {
      ex.printStackTrace();
    } catch (InvocationTargetException ex) {
      ex.printStackTrace();
    }
  }
}
Résultat :
C:\java\src>javac com/jmdoudoux/test/reflection/TestExecuterMethode.java
com\jmdoudoux\test\reflection\TestExecuterMethode.java:14: warning:
non-varargs call of varargs method with inexact argument type for last parameter;
   m.invoke(maClasse, null);
             ^
  cast to Object for a varargs call
  cast to Object[] for a non-varargs call and to suppress this warning
1 warning

Le compilateur signale par son warning qu'il n'est pas en mesure de déterminer si la valeur null concerne la valeur du premier élément du varargs ou un tableau d'objets null.

Le résultat à l'exécution est tout de même celui attendu.

Résultat :
Methode : public void fr.jmdoudoux.dej.reflection.MaClasse.maMethode()
maMethode sans param

 

23.4.2. La gestion d'une exception levée par la méthode invoquée

Lors de l'invocation dynamique d'une méthode en utilisant la méthode invoke(), si une exception est levée par la méthode invoquée alors celle-ci est chainée dans une exception de type java.lang.reflect.InvocationTargetException.

Exemple :
package fr.jmdoudoux.dej.reflection;
      
public class MaClasse {

  public void maMethode(int param1) {
    System.out.println("maMethode avec int:"+param1);
    if (param1 == 10) {
      throw new IllegalStateException("La valeur 10 n'est pas permise.");
    }
  }
}
Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.reflection;
      
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestExecuterMethode {

  public static void main(String[] args) {
    MaClasse maClasse = new MaClasse();
    try {
      Class<?> c = maClasse.getClass();
      Method m = c.getMethod("maMethode", Integer.TYPE);
      System.out.format("Methode : %s%n", m.toGenericString());
      m.invoke(maClasse, Integer.valueOf(10));        
    } catch (NoSuchMethodException x) {
      x.printStackTrace();
    } catch (IllegalArgumentException ex) {
      ex.printStackTrace();
    } catch (IllegalAccessException ex) {
      ex.printStackTrace();
    } catch (InvocationTargetException ex) {
      ex.printStackTrace();
      Throwable cause = ex.getCause();
      System.out.println("Cause : "+cause.getMessage());
    }
  }
}
Résultat :
Methode : public void fr.jmdoudoux.dej.reflection.MaClasse.maMethode(int)
maMethode avec int:10
java.lang.reflect.InvocationTargetException
      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      at java.lang.reflect.Method.invoke(Method.java:601)
      at fr.jmdoudoux.dej.reflection.TestExecuterMethode.main(TestExecuterMethode.java:14)
Caused by: java.lang.IllegalStateException: La valeur 10 n'est pas permise.
      at fr.jmdoudoux.dej.reflection.MaClasse.maMethode(MaClasse.java:34)
      ... 5 more
Cause : La valeur 10 n'est pas permise.

Pour obtenir l'exception levée par la méthode exécutée, il faut utiliser la méthode getCause() de l'exception InvocationTargetException.

 

23.4.3. L'invocation d'une méthode statique

Si la méthode à invoquer est static alors il faut passer null comme valeur du premier paramètre qui correspond à l'instance à invoquer.

Exemple :
package fr.jmdoudoux.dej.reflection;
      
import java.lang.reflect.Method;

public class TestExecuterMethode {

  public static void main(String[] args) {
    MaClasse maClasse = new MaClasse();
    try {
      Method method = maClasse.getClass().getDeclaredMethod("maMethodeStatic", null);
      Object retour = method.invoke(null);
      System.out.println("Valeur de retour = " + retour);
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
}
Résultat :
maMethodeStatic sans param 
Valeur de retour = null

 

23.4.4. L'accès aux méthodes privées

Les méthodes getMethod(String name, Class[] parameterTypes) et getMethods() ne permettent de renvoyer que des méthodes publiques. Pour obtenir des méthodes privées, il faut utiliser les méthodes getDeclaredMethod() et getDeclaredMethods().

Par défaut, l'invocation dynamique d'une méthode inaccessible, par exemple déclarée private, lève une exception de type IllegalAccessException.

Exemple :
package fr.jmdoudoux.dej.reflection;
      
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestExecuterMethode {

  public static void main(String[] args) {
    MaClasse maClasse = new MaClasse();
    try {
      Method method = maClasse.getClass().getDeclaredMethod("maMethodePrivee", null);
      Object retour = method.invoke(maClasse);
      System.out.println("Valeur de retour = " + retour);
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
}
Résultat :
java.lang.IllegalAccessException:
Class fr.jmdoudoux.dej.reflection.TestExecuterMethode can not access a member
of class fr.jmdoudoux.dej.reflection.MaClasse with modifiers "private"
       at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:95)
       at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:261)
       at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:253)
       at java.lang.reflect.Method.invoke(Method.java:594)
       at fr.jmdoudoux.dej.reflection.TestExecuterMethode.main(TestExecuterMethode.java:13)

La méthode getDeclaredMethod() ne peut qu'accéder aux méthodes qui sont déclarées dans la classe elle-même : elle ne permet pas d'accéder aux méthodes des super-classes.

Par défaut, les restrictions d'accès à une méthode s'appliquent aussi lors de l'utilisation de l'API Reflection.

La classe Method hérite de la classe AccessibleObject qui possèdent la méthode setAccessible(). Elle attend en paramètre un booléen : elle permet avec la valeur true de retirer les vérifications d'accessibilité qui seront faites pour permettre un accès par introspection à la méthode encapsulée. Cela permet de contourner les vérifications d'accès et ainsi d'accéder à une méthode déclarée privée, protected ou package-private uniquement en utilisant l'API Reflection.

Exemple :
package fr.jmdoudoux.dej.reflection;
      
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;

public class TestPrivateMethodInvoke {

  public static void main(String[] args) {
    MaClasse maClasse = new MaClasse();
    try {
      Method method = maClasse.getClass().getDeclaredMethod("maMethodePrivee", null);
      method.setAccessible(true);
      Object retour = method.invoke(maClasse);
      Logger.getLogger(TestPrivateMethodInvoke.class.getName())
        .log(Level.INFO, "Valeur de retour = " + retour);
    } catch (Exception ex) {
      Logger.getLogger(TestPrivateMethodInvoke.class.getName())
        .log(Level.SEVERE, null, ex);
    }
  }
}

 

23.4.5. L'invocation dynamique d'une méthode avec type generic

Il est nécessaire de tenir compte de plusieurs points lors de l'utilisation de l'introspection pour invoquer une méthode dont le type d'un paramètre est un type générique.

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.reflection;
      
public class MaClasseGenerique<T> {

  public void maMethode(T t) {
    System.out.println("maMethode "+t);
  }
}
Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.reflection;
      
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestExecuterMethode {
  public static void main(String[] args) {
    MaClasseGenerique<Integer> maClasse = new MaClasseGenerique<Integer>();
    try {
      Class<?> c = maClasse.getClass();
      Method m = c.getMethod("maMethode", Integer.class);
      System.out.format("Methode : %s%n", m.toGenericString());
    } catch (NoSuchMethodException x) {
      x.printStackTrace();
    } catch (IllegalArgumentException ex) {
      ex.printStackTrace();
    }
  }
}
Résultat :
java.lang.NoSuchMethodException:
fr.jmdoudoux.dej.reflection.MaClasseGenerique.maMethode(java.lang.Integer)
       at java.lang.Class.getMethod(Class.java:1622)
       at fr.jmdoudoux.dej.reflection.TestExecuterMethode.main(TestExecuterMethode.java:12)

Bien que le type générique de la classe soit Integer, la méthode n'est pas trouvée par introspection en précisant le type Integer comme paramètre.

A cause de l'implémentation des generics qui utilise le type erasure, le type generic original est perdu à la compilation pour laisser le type Object. Lorsque le type de la méthode est un type généric, il faut le remplacer par le type Object lorsque l'API Reflection est utilisée pour invoquer la méthode.

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.reflection;
      
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestExecuterMethode {

  public static void main(String[] args) {
    MaClasseGenerique<Integer> maClasse = new MaClasseGenerique<Integer>();
    try {
      Class<?> c = maClasse.getClass();
      Method m = c.getMethod("maMethode", Object.class);
      System.out.format("Methode : %s%n", m.toGenericString());
      m.invoke(maClasse, Integer.valueOf(100));
    } catch (NoSuchMethodException x) {
      x.printStackTrace();
    } catch (IllegalAccessException ex) {
      ex.printStackTrace();
    } catch (IllegalArgumentException ex) {
      ex.printStackTrace();
    } catch (InvocationTargetException ex) {
      ex.printStackTrace();
    }
  }
}
Résultat :
Methode : public void fr.jmdoudoux.dej.reflection.MaClasseGenerique.maMethode(T)
maMethode 100

 

23.5. L'API Reflection et le SecurityManager

L'API Reflection permet la mise en oeuvre de puissantes fonctionnalités : c'est une des raisons qui fait qu'elle est fréquemment utilisée par de nombreux frameworks parmi lesquels Spring ou Hibernate.

Cependant certaines fonctionnalités peuvent aussi être utilisées à des fins malveillantes qui peuvent nuire à la sécurité d'une application (invocation de méthodes, modifications de la valeur de champs, ... même si les modificateurs de ces membres ne permettent normalement pas leur accès, ...).

Les accès à un objet en utilisant l'API Reflection se font en utilisant une implémentation de l'interface AccessibleObject. Pour contourner les vérifications de l'accessibilité aux éléments d'un objet, il faut mettre à true la propriété access en utilisant la méthode setAccessible(). Par contre cela ne désactive pas les vérifications faites par le SecurityManager, s'il y en a un d'activé.

Par défaut, aucun SecurityManager n'est activé dans une JVM. Pour en activer un, il faut soit :

  • utiliser l'option -Djava.security.manager au lancement de la JVM
  • créer une nouvelle instance de type SecurityManager() et la passer en paramètre de la méthode setSecurityManager de la classe System

Lorsqu'un SecurityManager est activé sur une JVM, il est nécessaire d'autoriser la permission de type ReflectPermission dont le nom est "suppressAccessChecks" pour pouvoir utiliser des fonctionnalités de l'API Reflection. Si cette permission n'est pas donnée, alors une exception est levée par la méthode checkPermission() lors de l'utilisation de ces fonctionnalités.

Exemple :
package fr.jmdoudoux.dej.reflection;
      
import java.lang.reflect.Method;

public class TestExecuterMethode {

  public static void main(String[] args) {
    System.setSecurityManager(new SecurityManager());
    MaClasse maClasse = new MaClasse();
    try {
      Method method = maClasse.getClass().getDeclaredMethod("maMethodePrivee", null);
      method.setAccessible(true);
      Object retour = method.invoke(maClasse);
      System.out.println("Valeur de retour = " + retour);
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
}
Résultat :
java.security.AccessControlException:
access denied ("java.lang.reflect.ReflectPermission" "suppressAccessChecks")
       at java.security.AccessControlContext.checkPermission(AccessControlContext.java:366)
       at java.security.AccessController.checkPermission(AccessController.java:555)
       at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
       at java.lang.reflect.AccessibleObject.setAccessible(AccessibleObject.java:128)
       at fr.jmdoudoux.dej.reflection.TestExecuterMethode.main(TestExecuterMethode.java:14)

Il est nécessaire de définir ou de modifier la politique de sécurité pour accorder la permission "suppressAccessChecks" à la classe java.lang.reflect.ReflectPermission.

Il est possible de définir son propre fichier qui contient la définition de la politique de sécurité à appliquer.

Le fichier TestExecuterMethode.policy
grant { 
  permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
}; 

Il faut préciser le fichier comme valeur de la propriété java.security.policy de la JVM.

Exemple :
package fr.jmdoudoux.dej.reflection;
      
import java.lang.reflect.Method;

public class TestExecuterMethode {

  public static void main(String[] args) {
    System.setProperty("java.security.policy",
      "file:/C:/java/TestReflection/src/TestExecuterMethode.policy");
    System.setSecurityManager(new SecurityManager());
    
    MaClasse maClasse = new MaClasse();
    try {
      Method method = maClasse.getClass().getDeclaredMethod("maMethodePrivee", null);
      method.setAccessible(true);
      Object retour = method.invoke(maClasse);
      System.out.println("Valeur de retour = " + retour);
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
}

Il est important de modifier la valeur de la propriété avant l'activation du SecurityManager sinon il faut ajouter une permission autorisant la modification des propriétés système.

Attention : il faut autoriser la permission "suppressAccessChecks" avec précaution en limitant son effet uniquement sur les classes connues pour en avoir besoin. Typiquement, dans l'exemple ci-dessus, cette permission est donnée à toutes les classes dans la JVM ce qui peut être à l'origine de problèmes de sécurité.

Une exception de type SecurityException est levée si la méthode setAccessible() est invoquée sur une instance de type Constructor pour la classe Class.

Exemple :
package fr.jmdoudoux.dej.reflection;
      
import java.lang.reflect.Constructor;

public class TestConstructeurClass {

  public static void main(String[] args) {
    Class classe = Class.class;
    try {
      Constructor constructeur = classe.getDeclaredConstructor();
      constructeur.setAccessible(true);
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
}
Résultat :
java.lang.SecurityException: Can not make a java.lang.Class constructor accessible
        at java.lang.reflect.AccessibleObject.setAccessible0(AccessibleObject.java:139)
        at java.lang.reflect.AccessibleObject.setAccessible(AccessibleObject.java:129)
        at fr.jmdoudoux.dej.reflection.TestPrivateConstructeurInvoke.main(
TestPrivateConstructeurInvoke.java:11)

 

23.6. L'utilisation de l'API Reflection sur les annotations

Les annotations permettent d'ajouter des métadonnées dans le code source Java. Ces métadonnées peuvent être exploitées dans le code source, à la compilation ou à l'exécution en utilisant l'API Reflection.

Elle permet d'accéder aux annotations définies sur un type, une méthode, un champ ou un paramètre de manière dynamique à l'exécution.

Pour pouvoir utiliser l'API Reflection sur une annotation à l'exécution, il est nécessaire que la définition de l'annotation soit faite avec l'annotation @Retention à laquelle la valeur RetentionPolicy.RUNTIME est utilisée en paramètre.

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.reflection;
      
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER, ElementType.FIELD})
public @interface MonAnnotation {
    public String name();
    public String value();  
}

 

23.6.1. Les annotations sur une classe

Telle que définie, l'annotation peut s'utiliser sur un type (une classe ou une interface).

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.reflection;
      
@MonAnnotation(nom="nom1", valeur="valeur1")
public class MaClasse {
}

Il est possible d'utiliser l'API Reflection pour accéder dynamiquement aux annotations utilisées sur une classe.

La méthode getAnnotations() de la classe Class permet d'obtenir un tableau de type Annotation qui contient toutes les annotations définies sur la classe.

Exemple :
package fr.jmdoudoux.dej.reflection;
      
import java.lang.annotation.Annotation;

public class TestGetAnnotations {

  public static void main(String args[]) {
    Class classeClass = MaClasse.class;
    Annotation[] annotations = classeClass.getAnnotations();
    for (Annotation annotation : annotations) {
      if (annotation instanceof MonAnnotation) {
        MonAnnotation monAnnotation = (MonAnnotation) annotation;
        System.out.println("nom    : " + monAnnotation.nom());
        System.out.println("valeur : " + monAnnotation.valeur());
      }
    }
  }
}
Résultat :
non    : nom1
valeur : valeur1

La méthode getAnnotation() de la classe Class permet d'obtenir une instance de type Annotation encapsulant l'annotation utilisée sur la classe dont le type correspond à celui passé en paramètre.

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.reflection;
      
import java.lang.annotation.Annotation;

public class TestGetAnnotations {

  public static void main(String args[]) {
    Class classeClass = MaClasse.class;
    Annotation annotation = classeClass.getAnnotation(MonAnnotation.class);
    if (annotation instanceof MonAnnotation) {
      MonAnnotation monAnnotation = (MonAnnotation) annotation;
      System.out.println("nom : " + monAnnotation.nom());
      System.out.println("valeur : " + monAnnotation.valeur());
    }
  }
}

 

23.6.2. Les annotations sur une méthode

Telle que définie, l'annotation peut s'utiliser sur une méthode.

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.reflection;

public class MaClasse {

  @MonAnnotation(nom="nom2", valeur="valeur2")
  public void maMethode() {
  }
}

Il est possible d'utiliser l'API Reflection pour accéder dynamiquement aux annotations utilisées sur une méthode.

La méthode getDeclaredAnnotations() de la classe Method permet d'obtenir un tableau de type Annotation qui contient toutes les annotations définies sur la méthode.

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.reflection;
      
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

public class TestGetAnnotations {

  public static void main(String args[]) {
    Class classeClass = MaClasse.class;
    try {
      Method maMethode = classeClass.getMethod("maMethode");
      Annotation[] annotations = maMethode.getDeclaredAnnotations();
      for (Annotation annotation : annotations) {
        if (annotation instanceof MonAnnotation) {
          MonAnnotation monAnnotation = (MonAnnotation) annotation;
          System.out.println("nom    : " + monAnnotation.nom());
          System.out.println("valeur : " + monAnnotation.valeur());
        }
      }
    } catch (NoSuchMethodException ex) {
      ex.printStackTrace();
    } catch (SecurityException ex) {
      ex.printStackTrace();
    }
  }
}
Résultat :
nom    : nom2
valeur : valeur2

La méthode getAnnotation() de la classe Method permet d'obtenir une instance de type Annotation encapsulant l'annotation utilisée sur la méthode dont le type est passé en paramètre.

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.reflection;
      
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

public class TestGetAnnotations {

  public static void main(String args[]) {
    Class classeClass = MaClasse.class;
    try {
      Method maMethode = classeClass.getMethod("maMethode");
      Annotation annotation = maMethode.getAnnotation(MonAnnotation.class);
      if (annotation instanceof MonAnnotation) {
        MonAnnotation monAnnotation = (MonAnnotation) annotation;
        System.out.println("non    : " + monAnnotation.nom());
        System.out.println("valeur : " + monAnnotation.valeur());
      }
    } catch (NoSuchMethodException ex) {
      ex.printStackTrace();
    } catch (SecurityException ex) {
      ex.printStackTrace();
    }
  }
}
Résultat :
non    : nom2
valeur : valeur2

 

23.6.3. Les annotations sur un paramètre d'une méthode

Telle que définie, l'annotation peut s'utiliser sur un paramètre d'une méthode.

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.reflection;
      
public class MaClasse {

  public void maMethode() {
    System.out.println("maMethode sans param");
  }
  
  public String maMethode(@MonAnnotation(nom="nom3", valeur="valeur3") String param1) {
    System.out.println("maMethode avec String:"+param1);
    return param1;
  }
}

Il est possible d'utiliser l'API Reflection pour accéder dynamiquement aux annotations utilisées sur les paramètres d'une méthode.

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.reflection;
      
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

public class TestGetAnnotations {

  public static void main(String args[]) {
    Class classeClass = MaClasse.class;
    try {
      Method maMethode = classeClass.getMethod("maMethode", String.class);
      Annotation[][] parameterAnnotations = maMethode.getParameterAnnotations();
      Class[] parameterTypes = maMethode.getParameterTypes();
      int i = 0;
      for (Annotation[] annotations : parameterAnnotations) {
        Class parameterType = parameterTypes[i++];
        System.out.println("type du paramètre "+i+" "+parameterType);
        for (Annotation annotation : annotations) {
          if (annotation instanceof MonAnnotation) {
            MonAnnotation monAnnotation = (MonAnnotation) annotation;
            System.out.println("nom    : " + monAnnotation.nom());
            System.out.println("valeur : " + monAnnotation.valeur());
          }
        }
      }
    } catch (NoSuchMethodException ex) {
      ex.printStackTrace();
    } catch (SecurityException ex) {
      ex.printStackTrace();
    }
  }
}
Résultat :
type du paramètre 1 class java.lang.String
nom    : nom3
valeur : valeur3

La méthode getParameterAnnotations() renvoie un tableau à deux dimensions de type Annotation qui contient pour chaque paramètre, les annotations qui lui sont associées.

 

23.6.4. Les annotations sur un champ

Telle que définie, l'annotation peut s'utiliser sur un champ d'une classe.

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.reflection;
      
public class MaClasse {

  @MonAnnotation(nom="nom4", valeur="valeur4")
  private String monChamp;  
}

Il est possible d'utiliser l'API Reflection pour accéder dynamiquement aux annotations utilisées sur un champ.

La méthode getDeclaredAnnotations() de la classe Field permet d'obtenir un tableau de type Annotation qui contient toutes les annotations définies sur le champ.

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.reflection;
      
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.logging.Level;
import java.util.logging.Logger;

public class TestGetAnnotations {

  public static void main(String args[]) {
    Class classeClass = MaClasse.class;
    try {
      Field monChamp = classeClass.getDeclaredField("monChamp");
      if (monChamp != null) {
        Annotation[] annotations = monChamp.getDeclaredAnnotations();
        for (Annotation annotation : annotations) {
          if (annotation instanceof MonAnnotation) {
            MonAnnotation monAnnotation = (MonAnnotation) annotation;
            System.out.println("non    : " + monAnnotation.nom());
            System.out.println("valeur : " + monAnnotation.valeur());
          }
        }
      }
    } catch (NoSuchFieldException ex) {
      ex.printStackTrace();
    } catch (SecurityException ex) {
      ex.printStackTrace();
    }
  }
}
Résultat :
non    : nom4
valeur : valeur4

La méthode getAnnotation() de la classe Field permet d'obtenir une instance de type Annotation encapsulant l'annotation utilisée sur le champ dont le type est passé en paramètre.

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.reflection;
      
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.logging.Level;
import java.util.logging.Logger;

public class TestGetAnnotations {

  public static void main(String args[]) {
    Class classeClass = MaClasse.class;
    try {
      Field monChamp = classeClass.getDeclaredField("monChamp");
      if (monChamp != null) {
        Annotation annotation = monChamp.getAnnotation(MonAnnotation.class);
        if (annotation != null && annotation instanceof MonAnnotation) {
          MonAnnotation monAnnotation = (MonAnnotation) annotation;
          System.out.println("nom    : " + monAnnotation.nom());
          System.out.println("valeur : " + monAnnotation.valeur());
        }
      }
    } catch (NoSuchFieldException ex) {
      ex.printStackTrace();
    } catch (SecurityException ex) {
      ex.printStackTrace();
    }
  }
}
Résultat :
non    : nom4
valeur : valeur4

 

 


[ Précédent ] [ Sommaire ] [ Suivant ] [Télécharger ]      [Accueil ]

78 commentaires Donner une note à l´article (5)

 

Copyright (C) 1999-2022 Jean-Michel DOUDOUX. Vous pouvez copier, redistribuer et/ou modifier ce document selon les termes de la Licence de Documentation Libre GNU, Version 1.1 ou toute autre version ultérieure publiée par la Free Software Foundation; les Sections Invariantes étant constitués du chapitre Préambule, aucun Texte de Première de Couverture, et aucun Texte de Quatrième de Couverture. Une copie de la licence est incluse dans la section GNU FreeDocumentation Licence. La version la plus récente de cette licence est disponible à l'adresse : GNU Free Documentation Licence.