Réflexion Java offre la possibilité d’inspecter et de modifier le comportement d’exécution de l’application. La réflexion en Java est l’un des sujets avancés de Java de base. En utilisant la réflexion Java, nous pouvons inspecter une classe, une interface, un enum, obtenir leur structure, leurs méthodes et les informations sur les champs à 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 et changer les valeurs des champs.
Réflexion Java
- Réflexion en Java
- Réflexion Java pour les Classes
- Obtenir l’objet de la classe
- Obtenir la superclasse
- Obtenir les classes membres publiques
- Obtenir les classes déclarées
- Obtenir la classe déclarante
- Obtenir le nom du package
- Obtenir les modificateurs de classe
- Obtenir les paramètres de type
- Obtenir les interfaces implémentées
- Obtenir toutes les méthodes publiques
- Obtenir tous les constructeurs publics
- Obtenir tous les champs publics
- Obtenir toutes les annotations
- Réflexion Java pour les champs
- Réflexion Java pour les méthodes
- Réflexion Java pour les constructeurs
- La réflexion Java pour les annotations
La réflexion en Java est un concept très puissant et il est peu utilisé dans la programmation normale, mais il est l’épine dorsale de la plupart des frameworks Java et J2EE. Certains des frameworks qui utilisent la réflexion Java sont :
- JUnit – utilise la réflexion pour analyser l’annotation @Test afin d’obtenir les méthodes de test et les invoquer.
- Spring – injection de dépendance, en savoir plus sur Injection de dépendance Spring
- Tomcat conteneur web pour acheminer la requête vers le module correct en analysant leurs fichiers web.xml et l’URI de la requête.
- Eclipse complétion automatique des noms de méthodes
- Struts
- Hibernate
La liste est infinie et ils utilisent tous la réflexion Java, car tous ces frameworks n’ont aucune connaissance et 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 interfaces en raison des inconvénients suivants.
- Performances médiocres – Comme la réflexion Java résout les types de manière dynamique, elle implique des traitements tels que la recherche dans le chemin de classe pour trouver la classe à charger, ce qui entraîne une lenteur des performances.
- Restrictions de sécurité – La réflexion nécessite des autorisations d’exécution qui peuvent ne pas être disponibles pour le système s’exécutant sous un gestionnaire de sécurité. Cela peut provoquer l’échec de votre application à l’exécution en raison du gestionnaire de sécurité.
- Problèmes de sécurité – En utilisant la réflexion, nous pouvons accéder à une partie du code à laquelle nous ne sommes pas censés accéder. Par exemple, nous pouvons accéder aux champs privés d’une classe et en changer la valeur. Cela peut représenter une menace sérieuse pour la sécurité et provoquer un comportement anormal de votre application.
- Entretien élevé – Le code de réflexion est difficile à comprendre et à déboguer. De plus, les problèmes avec le code ne peuvent pas être détectés au moment de la compilation car les classes peuvent ne pas être disponibles, le rendant moins flexible et difficile à entretenir.
En Java, chaque objet est soit un type primitif, soit une référence. Toutes les classes, énumérations et 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 immutable de java.lang.Class
qui fournit des méthodes pour examiner les propriétés d’exécution de l’objet, créer de nouveaux objets, invoquer ses méthodes et obtenir/définir les champs de l’objet. Dans cette section, nous examinerons les méthodes importantes de Class. Pour plus de commodité, je crée quelques classes et interfaces avec une hiérarchie d’héritage.
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");
}
// classe publique interne
public class BaseClassInnerClass{}
// membre énumération publique
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;
}
// classes internes
public class ConcreteClassPublicClass{}
private class ConcreteClassPrivateClass{}
protected class ConcreteClassProtectedClass{}
class ConcreteClassDefaultClass{}
// membre énumération
enum ConcreteClassDefaultEnum{}
public enum ConcreteClassPublicEnum{}
// interface membre
public interface ConcreteClassPublicInterface{}
}
Jetons un coup d’œil à quelques-unes des méthodes d’inspection importantes pour les classes.
Obtenir l’objet Classe
Nous pouvons obtenir la Classe d’un objet en utilisant trois méthodes – à travers la variable statique class
, en utilisant la méthode getClass()
de l’objet et java.lang.Class.forName(String fullyClassifiedClassName)
. Pour les types primitifs et les tableaux, nous pouvons utiliser la variable statique class
. Les classes Wrapper fournissent une autre variable statique TYPE
pour obtenir la classe.
// Obtenir la Classe en utilisant la réflexion
Class > concreteClass = ConcreteClass.class;
concreteClass = new ConcreteClass(5).getClass();
try {
// la méthode ci-dessous est utilisée la plupart du temps dans des frameworks comme JUnit
// injection de dépendance Spring, conteneur web Tomcat
// auto-complétion Eclipse des noms de méthode, hibernate, Struts2 etc.
//parce que ConcreteClass n'est pas disponible au moment de la compilation
concreteClass = Class.forName("com.journaldev.reflection.ConcreteClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(concreteClass.getCanonicalName()); // prints com.journaldev.reflection.ConcreteClass
//pour les types primitifs, les classes Wrapper et les tableaux
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()
retourne le nom canonique de la classe sous-jacente. Remarquez que java.lang.Class utilise les 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 le Tutoriel sur les génériques en Java pour en savoir plus sur les génériques et leurs jokers.
Obtenir la Super Classe
La méthode getSuperclass() sur un objet Class retourne la super classe de la classe. Si cette Classe représente soit la classe Object, une interface, un type primitif ou void, alors null est retourné. Si cet objet représente une classe de tableau alors l’objet Class représentant la classe Object est retourné.
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 les Classes Membres Publiques
La méthode getClasses()
d’une représentation de classe d’objet renvoie un tableau contenant des objets de classe représentant toutes les classes, interfaces et énumérations publiques qui sont des 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 publics ou si cet objet Class représente un type primitif, une classe de tableau ou void.
Class >[] classes = concreteClass.getClasses();
//[classe com.journaldev.reflection.ConcreteClass$ConcreteClassPublicClass,
//classe com.journaldev.reflection.ConcreteClass$ConcreteClassPublicEnum,
//interface com.journaldev.reflection.ConcreteClass$ConcreteClassPublicInterface,
//classe com.journaldev.reflection.BaseClass$BaseClassInnerClass,
//classe com.journaldev.reflection.BaseClass$BaseClassMemberEnum]
System.out.println(Arrays.toString(classes));
Obtenir les classes déclarées
getDeclaredClasses()
méthode renvoie un tableau d’objets Class reflétant toutes les classes et interfaces déclarées en tant que membres de la classe représentée par cet objet Class. Le tableau retourné n’inclut pas les classes déclarées dans les classes héritées et les interfaces.
//obtenir toutes les classes, interfaces et énumérations explicitement déclarées dans ConcreteClass
Class >[] explicitClasses = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredClasses();
//imprime [classe com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultClass,
//classe com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultEnum,
//classe com.journaldev.reflection.ConcreteClass$ConcreteClassPrivateClass,
//classe com.journaldev.reflection.ConcreteClass$ConcreteClassProtectedClass,
//classe com.journaldev.reflection.ConcreteClass$ConcreteClassPublicClass,
//classe com.journaldev.reflection.ConcreteClass$ConcreteClassPublicEnum,
//interface com.journaldev.reflection.ConcreteClass$ConcreteClassPublicInterface]
System.out.println(Arrays.toString(explicitClasses));
Obtenir la classe déclarante
getDeclaringClass()
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");
//imprime com.journaldev.reflection.ConcreteClass
System.out.println(innerClass.getDeclaringClass().getCanonicalName());
System.out.println(innerClass.getEnclosingClass().getCanonicalName());
Obtenir le nom du package
getPackage()
méthode renvoie le package de cette classe. Le chargeur de classes de cette classe est utilisé pour trouver le package. Nous pouvons invoquer la méthode getName()
de Package pour obtenir le nom du package.
//imprime "com.journaldev.reflection"
System.out.println(Class.forName("com.journaldev.reflection.BaseInterface").getPackage().getName());
Obtenir les modificateurs de classe
getModifiers()
méthode retourne la représentation entière des modificateurs de classe, nous pouvons utiliser la méthode java.lang.reflect.Modifier.toString()
pour l’obtenir sous forme de chaîne telle qu’utilisée dans le code source.
System.out.println(Modifier.toString(concreteClass.getModifiers())); //prints "public"
//imprime "public abstract interface"
System.out.println(Modifier.toString(Class.forName("com.journaldev.reflection.BaseInterface").getModifiers()));
Obtenir les paramètres de type
getTypeParameters()
retourne le tableau de TypeVariable s’il existe des paramètres de type associés à la classe. Les paramètres de type sont renvoyés dans le même ordre que déclarés.
//Obtenir les paramètres de type (génériques)
TypeVariable >[] typeParameters = Class.forName("java.util.HashMap").getTypeParameters();
for(TypeVariable > t : typeParameters)
System.out.print(t.getName()+",");
Obtenir les interfaces implémentées
getGenericInterfaces()
méthode retourne le tableau d’interfaces implémentées par la classe avec des informations de type générique. 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();
//imprime "[java.util.Map, interface java.lang.Cloneable, interface java.io.Serializable]"
System.out.println(Arrays.toString(interfaces));
//imprime "[interface java.util.Map, interface java.lang.Cloneable, interface java.io.Serializable]"
System.out.println(Arrays.toString(Class.forName("java.util.HashMap").getInterfaces()));
Obtenez tous les méthodes publiques
getMethods()
la méthode retourne le tableau des méthodes publiques de la classe, y compris les méthodes publiques de ses superclasses et interfaces super.
Method[] publicMethods = Class.forName("com.journaldev.reflection.ConcreteClass").getMethods();
//imprime les méthodes publiques de la ConcreteClass, BaseClass, Object
System.out.println(Arrays.toString(publicMethods));
Obtenez tous les constructeurs publics
getConstructors()
la méthode retourne la liste des constructeurs publics de la référence de classe de l’objet.
//Obtenez tous les constructeurs publics
Constructor >[] publicConstructors = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructors();
//imprime les constructeurs publics de la ConcreteClass
System.out.println(Arrays.toString(publicConstructors));
Obtenez tous les champs publics
getFields()
la méthode retourne le tableau des champs publics de la classe, y compris les champs publics de ses superclasses et interfaces super.
//Obtenez tous les champs publics
Field[] publicFields = Class.forName("com.journaldev.reflection.ConcreteClass").getFields();
//imprime les champs publics de la classe concrète, de sa superclasse et de ses super interfaces
System.out.println(Arrays.toString(publicFields));
Obtenez toutes les annotations
getAnnotations()
méthode retourne toutes les annotations pour l’élément, nous pouvons l’utiliser avec la classe, les champs et les méthodes aussi. Notez que seules les annotations disponibles avec la réflexion sont avec une politique de rétention de RUNTIME, consultez Tutoriel sur les annotations Java. Nous examinerons cela plus en détail dans les sections suivantes.
java.lang.annotation.Annotation[] annotations = Class.forName("com.journaldev.reflection.ConcreteClass").getAnnotations();
//imprime [@java.lang.Deprecated()]
System.out.println(Arrays.toString(annotations));
L’API de réflexion fournit plusieurs méthodes pour analyser les champs de classe et modifier leurs valeurs à l’exécution, dans cette section nous examinerons certaines des fonctions de réflexion couramment utilisées pour les méthodes.
Obtenir un 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 à travers la méthode getField()
. Cette méthode recherche le champ dans la référence de classe spécifiée, puis dans les super interfaces et ensuite 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 lance NoSuchFieldException.
Classe déclarant le champ
Nous pouvons utiliser getDeclaringClass()
de l’objet champ 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, il 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 d’un 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() retourne un objet, donc si le champ est de type primitif, elle retourne la Classe Wrapper correspondante. Si le champ est statique, nous pouvons passer un objet null dans la méthode get(). Il existe plusieurs méthodes set*() pour définir un objet sur le champ ou définir différents types de types primitifs sur le champ. Nous pouvons obtenir le type de champ, puis invoquer la fonction correcte pour définir la valeur du champ correctement. Si le champ est final, les méthodes set() lèvent java.lang.IllegalAccessException.
Obtenir/Définir la valeur d’un 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 la vérification d’accès java pour les modificateurs de champ.
Field privateField = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredField("privateString");
//désactivation de la vérification d'accès avec l'appel de méthode ci-dessous
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"
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 façons d’obtenir une méthode, d’invoquer une méthode et d’accéder aux méthodes privées.
Obtenir une méthode publique
Nous pouvons utiliser getMethod() pour obtenir une méthode publique de la classe, nous devons passer le nom de la méthode et les types de paramètres de la méthode. Si la méthode n’est pas trouvée dans la classe, l’API de réflexion recherche la méthode dans la superclasse. Dans l’exemple ci-dessous, je récupère 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);
// obtenir les types de paramètres de la méthode, imprime "[class java.lang.Object, class java.lang.Object]"
System.out.println(Arrays.toString(method.getParameterTypes()));
// obtenir le type de retour de la méthode, retourne "class java.lang.Object", référence de classe pour void
System.out.println(method.getReturnType());
// obtenir les modificateurs de méthode
System.out.println(Modifier.toString(method.getModifiers())); //prints "public"
Invocation de méthode publique
Nous pouvons utiliser la méthode invoke() de l’objet Method pour invoquer une méthode, dans le code d’exemple ci-dessous, j’invoque 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.
Invocation de 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 la méthode3() de BaseClass qui est statique et n’a pas de paramètres.
//invoquer une méthode privée
Method method = Class.forName("com.journaldev.reflection.BaseClass").getDeclaredMethod("method3", null);
method.setAccessible(true);
method.invoke(null, null); //prints "Method3"
La API de réflexion fournit des méthodes pour obtenir les constructeurs d’une classe à des fins d’analyse, et nous pouvons créer de nouvelles instances de la classe en invoquant 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 la classe de l’objet pour obtenir un constructeur public spécifique. L’exemple ci-dessous montre comment obtenir le constructeur de la classe ConcreteClass définie 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);
//obtenir les paramètres du constructeur
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 en Utilisant le Constructeur
Nous pouvons utiliser la méthode newInstance() sur l’objet constructeur pour instancier une nouvelle instance de la classe. Comme nous utilisons la réflexion lorsque nous n’avons pas les informations sur les classes au moment de la compilation, nous pouvons l’assigner à 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);
//obtenir les paramètres du constructeur
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 myMap = (HashMap) hashMapConstructor.newInstance(null);
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 maintenant elles sont largement utilisées dans des frameworks comme Spring et Hibernate. L’API Reflection a également été étendue pour fournir un support d’analyse des annotations à l’exécution. En utilisant l’API Reflection, nous pouvons analyser les annotations dont la politique de rétention est Runtime. J’ai déjà rédigé un tutoriel détaillé sur les annotations et comment nous pouvons utiliser l’API Reflection pour analyser les annotations, donc je vous suggère de consulter Tutoriel sur les Annotations Java. C’est tout pour l’exemple de tutoriel sur la réflexion Java, j’espère que vous avez apprécié le tutoriel et compris l’importance de l’API Reflection Java.
Source:
https://www.digitalocean.com/community/tutorials/java-reflection-example-tutorial