Java Tutoriel d'exemple de réflexion

De Get Docs
Aller à :navigation, rechercher

Java Reflection permet d'inspecter et de modifier le comportement d'exécution de l'application. La réflexion dans Java est l'un des sujets avancés du noyau java. En utilisant la réflexion java, nous pouvons inspecter une classe, interface, enum, obtenir leurs informations sur la structure, les méthodes et les champs au moment de l'exécution, même si la classe n'est pas accessible au moment de la compilation. Nous pouvons également utiliser la réflexion pour instancier un objet, invoquer ses méthodes, modifier les valeurs des champs.

Java Réflexion

[1]

  1. Réflexion dans Java
  2. Java Réflexion pour les classes
  3. Java Réflexion pour les champs
  4. Java Réflexion pour les méthodes
  5. Java Réflexion pour les constructeurs
  6. Java Réflexion pour les annotations

[2]

  1. Réflexion dans Java

La réflexion dans Java est un concept très puissant et peu utile dans la programmation normale, mais c'est l'épine dorsale de la plupart des frameworks Java, J2EE. Certains des frameworks qui utilisent la réflexion java sont :

  1. JUnit - utilise la réflexion pour analyser l'annotation @Test afin d'obtenir les méthodes de test, puis de l'invoquer.
  2. Spring - injection de dépendances, en savoir plus sur Spring Injection de dépendances
  3. Conteneur Web Tomcat pour transmettre la demande au module correct en analysant leurs fichiers web.xml et l'URI de la demande.
  4. Eclipse complétion automatique des noms de méthodes
  5. Entretoises
  6. Hiberner

La liste est interminable et ils utilisent tous la réflexion java car tous ces frameworks n'ont aucune connaissance ni accès aux classes définies par l'utilisateur, aux interfaces, à leurs méthodes, etc. Nous ne devrions pas utiliser la réflexion dans la programmation normale où nous avons déjà accès aux classes et aux interfaces en raison des inconvénients suivants.

  • Mauvaises performances - Étant donné que la réflexion java résout les types de manière dynamique, elle implique un traitement tel que l'analyse du chemin de classe pour trouver la classe à charger, ce qui ralentit les performances.
  • Restrictions de sécurité - Reflection nécessite des autorisations d'exécution qui peuvent ne pas être disponibles pour le système exécuté sous le gestionnaire de sécurité. Cela peut entraîner l'échec de votre application lors de l'exécution à cause du gestionnaire de sécurité.
  • Problèmes de sécurité - En utilisant la réflexion, nous pouvons accéder à une partie du code auquel nous ne sommes pas censés accéder, par exemple nous pouvons accéder aux champs privés d'une classe et modifier sa valeur. Cela peut constituer une menace sérieuse pour la sécurité et entraîner un comportement anormal de votre application.
  • Maintenance élevée - Le code de réflexion est difficile à comprendre et à déboguer, et aucun problème avec le code ne peut être trouvé au moment de la compilation car les classes peuvent ne pas être disponibles, ce qui le rend moins flexible et difficile à maintenir.
  1. Java Réflexion pour les classes

Dans java, chaque objet est soit un type primitif, soit une référence. Toutes les classes, énumérations, tableaux sont des types de référence et héritent de java.lang.Object. Les types primitifs sont - boolean, byte, short, int, long, char, float et double. java.lang.Class est le point d'entrée pour toutes les opérations de réflexion. Pour chaque type d'objet, JVM instancie une instance immuable de java.lang.Class qui fournit des méthodes pour examiner les propriétés d'exécution de l'objet et créer de nouveaux objets, invoquer sa méthode et obtenir/définir les champs d'objet. Dans cette section, nous examinerons les méthodes importantes de Class. Pour plus de commodité, je crée des classes et des interfaces avec la hiérarchie inheritance.

package com.journaldev.reflection;

public interface BaseInterface {
    
    public int interfaceInt=0;
    
    void method1();
    
    int method2(String str);
}
package com.journaldev.reflection;

public class BaseClass {

    public int baseInt;
    
    private static void method3(){
        System.out.println("Method3");
    }
    
    public int method4(){
        System.out.println("Method4");
        return 0;
    }
    
    public static int method5(){
        System.out.println("Method5");
        return 0;
    }
    
    void method6(){
        System.out.println("Method6");
    }
    
    // inner public class
    public class BaseClassInnerClass{}
        
    //member public enum
    public enum BaseClassMemberEnum{}
}
package com.journaldev.reflection;

@Deprecated
public class ConcreteClass extends BaseClass implements BaseInterface {

    public int publicInt;
    private String privateString="private string";
    protected boolean protectedBoolean;
    Object defaultObject;
    
    public ConcreteClass(int i){
        this.publicInt=i;
    }

    @Override
    public void method1() {
        System.out.println("Method1 impl.");
    }

    @Override
    public int method2(String str) {
        System.out.println("Method2 impl.");
        return 0;
    }
    
    @Override
    public int method4(){
        System.out.println("Method4 overriden.");
        return 0;
    }
    
    public int method5(int i){
        System.out.println("Method4 overriden.");
        return 0;
    }
    
    // inner classes
    public class ConcreteClassPublicClass{}
    private class ConcreteClassPrivateClass{}
    protected class ConcreteClassProtectedClass{}
    class ConcreteClassDefaultClass{}
    
    //member enum
    enum ConcreteClassDefaultEnum{}
    public enum ConcreteClassPublicEnum{}
    
    //member interface
    public interface ConcreteClassPublicInterface{}

}

Regardons quelques-unes des méthodes de réflexion importantes pour les classes.

Obtenir un objet de classe

Nous pouvons obtenir la classe d'un objet en utilisant trois méthodes - via une variable statique class, utilisant getClass() méthode d'objet et java.lang.Class.forName(String fullyClassifiedClassName). Pour les types primitifs et les tableaux, nous pouvons utiliser une variable statique class. Les classes wrapper fournissent une autre variable statique TYPE pour obtenir la classe.

// Get Class using reflection
Class<?> concreteClass = ConcreteClass.class;
concreteClass = new ConcreteClass(5).getClass();
try {
    // below method is used most of the times in frameworks like JUnit
    //Spring dependency injection, Tomcat web container
    //Eclipse auto completion of method names, hibernate, Struts2 etc.
    //because ConcreteClass is not available at compile time
    concreteClass = Class.forName("com.journaldev.reflection.ConcreteClass");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}
System.out.println(concreteClass.getCanonicalName()); // prints com.journaldev.reflection.ConcreteClass

//for primitive types, wrapper classes and arrays
Class<?> booleanClass = boolean.class;
System.out.println(booleanClass.getCanonicalName()); // prints boolean

Class<?> cDouble = Double.TYPE;
System.out.println(cDouble.getCanonicalName()); // prints double

Class<?> cDoubleArray = Class.forName("[D");
System.out.println(cDoubleArray.getCanonicalName()); //prints double[]

Class<?> twoDStringArray = String[][].class;
System.out.println(twoDStringArray.getCanonicalName()); // prints java.lang.String[][]

getCanonicalName() renvoie le nom canonique de la classe sous-jacente. Notez que java.lang.Class utilise des génériques, cela aide les frameworks à s'assurer que la classe récupérée est une sous-classe de la classe de base du framework. Consultez Java Tutoriel sur les génériques pour en savoir plus sur les génériques et leurs caractères génériques.

Obtenez la super classe

La méthode getSuperclass() sur un objet Class renvoie la super classe de la classe. Si cette classe représente la classe Object, une interface, un type primitif ou void, alors null est renvoyé. Si cet objet représente une classe de tableau, l'objet Class représentant la classe Object est renvoyé.

Class<?> superClass = Class.forName("com.journaldev.reflection.ConcreteClass").getSuperclass();
System.out.println(superClass); // prints "class com.journaldev.reflection.BaseClass"
System.out.println(Object.class.getSuperclass()); // prints "null"
System.out.println(String[][].class.getSuperclass());// prints "class java.lang.Object"

Obtenir des classes de membres publics

getClasses() La méthode d'une représentation Class d'un objet renvoie un tableau contenant des objets Class représentant toutes les classes, interfaces et énumérations publiques qui sont membres de la classe représentée par cet objet Class. Cela inclut les membres de classe et d'interface publics hérités des superclasses et les membres de classe et d'interface publics déclarés par la classe. Cette méthode renvoie un tableau de longueur 0 si cet objet Class n'a pas de classes ou d'interfaces membres publiques ou si cet objet Class représente un type primitif, une classe tableau ou void.

Class<?>[] classes = concreteClass.getClasses();
//[class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicClass, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicEnum, 
//interface com.journaldev.reflection.ConcreteClass$ConcreteClassPublicInterface,
//class com.journaldev.reflection.BaseClass$BaseClassInnerClass, 
//class com.journaldev.reflection.BaseClass$BaseClassMemberEnum]
System.out.println(Arrays.toString(classes));

Obtenir des cours déclarés

getDeclaredClasses() renvoie un tableau d'objets Class reflétant toutes les classes et interfaces déclarées comme membres de la classe représentée par cet objet Class. Le tableau renvoyé n'inclut pas les classes déclarées dans les classes et interfaces héritées.

//getting all of the classes, interfaces, and enums that are explicitly declared in ConcreteClass
Class<?>[] explicitClasses = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredClasses();
//prints [class com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultClass, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultEnum, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPrivateClass, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassProtectedClass, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicClass, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicEnum, 
//interface com.journaldev.reflection.ConcreteClass$ConcreteClassPublicInterface]
System.out.println(Arrays.toString(explicitClasses));

Obtenir la classe de déclaration

getDeclaringClass() La méthode renvoie l'objet Class représentant la classe dans laquelle elle a été déclarée.

Class<?> innerClass = Class.forName("com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultClass");
//prints com.journaldev.reflection.ConcreteClass
System.out.println(innerClass.getDeclaringClass().getCanonicalName());
System.out.println(innerClass.getEnclosingClass().getCanonicalName());

Obtenir le nom du package

getPackage() La méthode renvoie le package pour cette classe. Le chargeur de classe de cette classe est utilisé pour trouver le package. Nous pouvons invoquer getName() méthode de Package pour obtenir le nom du package.

//prints "com.journaldev.reflection"
System.out.println(Class.forName("com.journaldev.reflection.BaseInterface").getPackage().getName());

Obtenir des modificateurs de classe

getModifiers() renvoie la représentation int des modificateurs de classe, nous pouvons utiliser java.lang.reflect.Modifier.toString() pour l'obtenir dans le format de chaîne utilisé dans le code source.

System.out.println(Modifier.toString(concreteClass.getModifiers())); //prints "public"
//prints "public abstract interface"
System.out.println(Modifier.toString(Class.forName("com.journaldev.reflection.BaseInterface").getModifiers())); 

Obtenir les paramètres de type

getTypeParameters() renvoie le tableau de TypeVariable s'il existe des paramètres Type associés à la classe. Les paramètres de type sont renvoyés dans le même ordre que celui déclaré.

//Get Type parameters (generics)
TypeVariable<?>[] typeParameters = Class.forName("java.util.HashMap").getTypeParameters();
for(TypeVariable<?> t : typeParameters)
System.out.print(t.getName()+",");

Obtenir les interfaces implémentées

getGenericInterfaces() La méthode renvoie le tableau des interfaces implémentées par la classe avec des informations de type génériques. Nous pouvons également utiliser getInterfaces() pour obtenir la représentation de classe de toutes les interfaces implémentées.

Type[] interfaces = Class.forName("java.util.HashMap").getGenericInterfaces();
//prints "[java.util.Map<K, V>, interface java.lang.Cloneable, interface java.io.Serializable]"
System.out.println(Arrays.toString(interfaces));
//prints "[interface java.util.Map, interface java.lang.Cloneable, interface java.io.Serializable]"
System.out.println(Arrays.toString(Class.forName("java.util.HashMap").getInterfaces()));        

Obtenir toutes les méthodes publiques

getMethods() method renvoie le tableau des méthodes publiques de la classe, y compris les méthodes publiques de ses superclasses et super interfaces.

Method[] publicMethods = Class.forName("com.journaldev.reflection.ConcreteClass").getMethods();
//prints public methods of ConcreteClass, BaseClass, Object
System.out.println(Arrays.toString(publicMethods));

Obtenir tous les constructeurs publics

getConstructors() La méthode renvoie la liste des constructeurs publics de la référence de classe de l'objet.

//Get All public constructors
Constructor<?>[] publicConstructors = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructors();
//prints public constructors of ConcreteClass
System.out.println(Arrays.toString(publicConstructors));

Obtenir tous les champs publics

getFields() La méthode renvoie le tableau des champs publics de la classe, y compris les champs publics de ses super classes et super interfaces.

//Get All public fields
Field[] publicFields = Class.forName("com.journaldev.reflection.ConcreteClass").getFields();
//prints public fields of ConcreteClass, it's superclass and super interfaces
System.out.println(Arrays.toString(publicFields));

Obtenir toutes les annotations

getAnnotations() La méthode renvoie toutes les annotations pour l'élément, nous pouvons également l'utiliser avec la classe, les champs et les méthodes. Notez que seules les annotations disponibles avec réflexion sont avec la politique de rétention de RUNTIME, consultez Java Annotations Tutorial. Nous examinerons cela plus en détail dans les sections ultérieures.

java.lang.annotation.Annotation[] annotations = Class.forName("com.journaldev.reflection.ConcreteClass").getAnnotations();
//prints [@java.lang.Deprecated()]
System.out.println(Arrays.toString(annotations));
  1. Java Réflexion pour les champs

L'API de réflexion fournit plusieurs méthodes pour analyser les champs de classe et modifier leurs valeurs lors de l'exécution. Dans cette section, nous examinerons certaines des fonctions de réflexion couramment utilisées pour les méthodes.

Obtenir le champ public

Dans la dernière section, nous avons vu comment obtenir la liste de tous les champs publics d'une classe. L'API de réflexion fournit également une méthode pour obtenir un champ public spécifique d'une classe via getField() méthode. Cette méthode recherche le champ dans la référence de classe spécifiée, puis dans les super interfaces, puis dans les super classes.

Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("interfaceInt");

L'appel ci-dessus renverra le champ de BaseInterface qui est implémenté par ConcreteClass. S'il n'y a pas de champ trouvé, il lève NoSuchFieldException.

Champ déclarant la classe

On peut utiliser getDeclaringClass() de l'objet field pour obtenir la classe déclarant le champ.

try {
    Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("interfaceInt");
    Class<?> fieldClass = field.getDeclaringClass();
    System.out.println(fieldClass.getCanonicalName()); //prints com.journaldev.reflection.BaseInterface
} catch (NoSuchFieldException | SecurityException e) {
    e.printStackTrace();
}

Obtenir le type de champ

La méthode getType() renvoie l'objet Class pour le type de champ déclaré, si le champ est de type primitif, elle renvoie l'objet de classe wrapper.

Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("publicInt");
Class<?> fieldType = field.getType();
System.out.println(fieldType.getCanonicalName()); //prints int          

Obtenir/Définir la valeur du champ public

Nous pouvons obtenir et définir la valeur d'un champ dans un objet en utilisant la réflexion.

Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("publicInt");
ConcreteClass obj = new ConcreteClass(5);
System.out.println(field.get(obj)); //prints 5
field.setInt(obj, 10); //setting field value to 10 in object
System.out.println(field.get(obj)); //prints 10

La méthode get() renvoie l'objet, donc si le champ est de type primitif, il renvoie la Classe Wrapper correspondante. Si le champ est statique, nous pouvons passer Object comme null dans la méthode get(). Il existe plusieurs méthodes set*() pour définir Object sur le champ ou définir différents types de types primitifs sur le champ. Nous pouvons obtenir le type de champ, puis appeler la fonction correcte pour définir correctement la valeur du champ. Si le champ est final, les méthodes set() lèvent java.lang.IllegalAccessException.

Obtenir/Définir la valeur du champ privé

Nous savons que les champs et méthodes privés ne peuvent pas être accessibles en dehors de la classe, mais en utilisant la réflexion, nous pouvons obtenir/définir la valeur du champ privé en désactivant le contrôle d'accès java pour les modificateurs de champ.

Field privateField = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredField("privateString");
//turning off access check with below method call
privateField.setAccessible(true);
ConcreteClass objTest = new ConcreteClass(1);
System.out.println(privateField.get(objTest)); // prints "private string"
privateField.set(objTest, "private string updated");
System.out.println(privateField.get(objTest)); //prints "private string updated"
  1. Java Réflexion pour les méthodes

En utilisant la réflexion, nous pouvons obtenir des informations sur une méthode et nous pouvons également l'invoquer. Dans cette section, nous apprendrons différentes manières d'obtenir une méthode, d'invoquer une méthode et d'accéder à des méthodes privées.

Obtenir la méthode publique

Nous pouvons utiliser getMethod() pour obtenir une méthode publique de classe, nous devons transmettre le nom de la méthode et les types de paramètres de la méthode. Si la méthode est introuvable dans la classe, l'API de réflexion recherche la méthode dans la superclasse. Dans l'exemple ci-dessous, j'obtiens la méthode put() de HashMap en utilisant la réflexion. L'exemple montre également comment obtenir les types de paramètres, les modificateurs de méthode et le type de retour d'une méthode.

Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
//get method parameter types, prints "[class java.lang.Object, class java.lang.Object]"
System.out.println(Arrays.toString(method.getParameterTypes()));
//get method return type, return "class java.lang.Object", class reference for void
System.out.println(method.getReturnType());
//get method modifiers
System.out.println(Modifier.toString(method.getModifiers())); //prints "public"

Invoquer la méthode publique

Nous pouvons utiliser la méthode invoke() de l'objet Method pour invoquer une méthode, dans l'exemple de code ci-dessous, j'appelle la méthode put sur HashMap en utilisant la réflexion.

Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
Map<String, String> hm = new HashMap<>();
method.invoke(hm, "key", "value");
System.out.println(hm); // prints {key=value}

Si la méthode est statique, nous pouvons passer NULL comme argument d'objet.

Invoquer des méthodes privées

Nous pouvons utiliser getDeclaredMethod() pour obtenir la méthode privée, puis désactiver la vérification d'accès pour l'invoquer, l'exemple ci-dessous montre comment nous pouvons invoquer method3() de BaseClass qui est statique et n'a pas de paramètres.

//invoking private method
Method method = Class.forName("com.journaldev.reflection.BaseClass").getDeclaredMethod("method3", null);
method.setAccessible(true);
method.invoke(null, null); //prints "Method3"
  1. Java Réflexion pour les constructeurs

L'API Reflection fournit des méthodes pour analyser les constructeurs d'une classe et nous pouvons créer de nouvelles instances de classe en appelant le constructeur. Nous avons déjà appris comment obtenir tous les constructeurs publics.

Obtenir le constructeur public

Nous pouvons utiliser la méthode getConstructor() sur la représentation de classe de l'objet pour obtenir un constructeur public spécifique. L'exemple ci-dessous montre comment obtenir le constructeur de ConcreteClass défini ci-dessus et le constructeur sans argument de HashMap. Il montre également comment obtenir le tableau des types de paramètres pour le constructeur.

Constructor<?> constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
//getting constructor parameters
System.out.println(Arrays.toString(constructor.getParameterTypes())); // prints "[int]"
        
Constructor<?> hashMapConstructor = Class.forName("java.util.HashMap").getConstructor(null);
System.out.println(Arrays.toString(hashMapConstructor.getParameterTypes())); // prints "[]"

Instancier un objet à l'aide du constructeur

Nous pouvons utiliser la méthode newInstance() sur l'objet constructeur pour instancier une nouvelle instance de la classe. Étant donné que nous utilisons la réflexion lorsque nous n'avons pas les informations sur les classes au moment de la compilation, nous pouvons l'affecter à Object, puis utiliser davantage la réflexion pour accéder à ses champs et invoquer ses méthodes.

Constructor<?> constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
//getting constructor parameters
System.out.println(Arrays.toString(constructor.getParameterTypes())); // prints "[int]"
        
Object myObj = constructor.newInstance(10);
Method myObjMethod = myObj.getClass().getMethod("method1", null);
myObjMethod.invoke(myObj, null); //prints "Method1 impl."

Constructor<?> hashMapConstructor = Class.forName("java.util.HashMap").getConstructor(null);
System.out.println(Arrays.toString(hashMapConstructor.getParameterTypes())); // prints "[]"
HashMap<String,String> myMap = (HashMap<String,String>) hashMapConstructor.newInstance(null);
  1. Réflexion pour les annotations

Les annotations ont été introduites dans Java 1.5 pour fournir des informations de métadonnées sur la classe, les méthodes ou les champs et sont maintenant largement utilisées dans des frameworks comme Spring et Hibernate. L'API de réflexion a également été étendue pour fournir un support pour analyser les annotations lors de l'exécution. En utilisant l'API de réflexion, nous pouvons analyser les annotations dont la politique de rétention est Runtime. J'ai déjà écrit un tutoriel détaillé sur les annotations et sur la façon dont nous pouvons utiliser l'API de réflexion pour analyser les annotations, je vous suggère donc de consulter Java Annotations Tutorial. C'est tout pour le tutoriel d'exemple de réflexion java. J'espère que vous avez aimé le tutoriel et compris l'importance de l'API de réflexion Java.