Recherche…


Introduction

Reflection est couramment utilisé par les programmes qui doivent pouvoir examiner ou modifier le comportement d'exécution des applications exécutées dans la JVM. L'API Java Reflection est utilisée à cette fin, où elle permet d'inspecter les classes, les interfaces, les champs et les méthodes à l'exécution, sans connaître leurs noms au moment de la compilation. Et il permet également d'instancier de nouveaux objets et d'invoquer des méthodes utilisant la réflexion.

Remarques

Performance

Gardez à l'esprit que la réflexion peut diminuer les performances, ne l'utilisez que si votre tâche ne peut être complétée sans réflexion.

A partir du tutoriel Java, l' API Reflection :

Étant donné que la réflexion implique des types résolus dynamiquement, certaines optimisations de la machine virtuelle Java ne peuvent pas être effectuées. Par conséquent, les opérations de réflexion ont des performances plus lentes que leurs homologues non réfléchissantes et doivent être évitées dans les sections de code appelées fréquemment dans les applications sensibles aux performances.

introduction

Les bases

L'API de réflexion permet de vérifier la structure de classe du code à l'exécution et d'appeler le code dynamiquement. Ceci est très puissant, mais il est également dangereux car le compilateur n'est pas capable de déterminer statiquement si les appels dynamiques sont valides.

Un exemple simple serait d'obtenir les constructeurs publics et les méthodes d'une classe donnée:

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

// This is a object representing the String class (not an instance of String!)
Class<String> clazz = String.class;

Constructor<?>[] constructors = clazz.getConstructors(); // returns all public constructors of String
Method[] methods = clazz.getMethods(); // returns all public methods from String and parents

Avec cette information, il est possible d'injecter l'objet et d'appeler différentes méthodes dynamiquement.

Réflexion et types génériques

Les informations de type génériques sont disponibles pour:

  • paramètres de méthode, en utilisant getGenericParameterTypes() .
  • types de retour de méthode, en utilisant getGenericReturnType() .
  • champs publics , en utilisant getGenericType .

L'exemple suivant montre comment extraire les informations de type générique dans les trois cas:

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

public class GenericTest {

    public static void main(final String[] args) throws Exception {
        final Method method = GenericTest.class.getMethod("testMethod", Map.class);
        final Field field = GenericTest.class.getField("testField");

        System.out.println("Method parameter:");
        final Type parameterType = method.getGenericParameterTypes()[0];
        displayGenericType(parameterType, "\t");

        System.out.println("Method return type:");
        final Type returnType = method.getGenericReturnType();
        displayGenericType(returnType, "\t");

        System.out.println("Field type:");
        final Type fieldType = field.getGenericType();
        displayGenericType(fieldType, "\t");

    }

    private static void displayGenericType(final Type type, final String prefix) {
        System.out.println(prefix + type.getTypeName());
        if (type instanceof ParameterizedType) {
            for (final Type subtype : ((ParameterizedType) type).getActualTypeArguments()) {
                displayGenericType(subtype, prefix + "\t");
            }
        }

    }

    public Map<String, Map<Integer, List<String>>> testField;

    public List<Number> testMethod(final Map<String, Double> arg) {
        return null;
    }

}

Cela se traduit par la sortie suivante:

Method parameter:
    java.util.Map<java.lang.String, java.lang.Double>
        java.lang.String
        java.lang.Double
Method return type:
    java.util.List<java.lang.Number>
        java.lang.Number
Field type:
    java.util.Map<java.lang.String, java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>>
        java.lang.String
        java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>
            java.lang.Integer
            java.util.List<java.lang.String>
                java.lang.String

Invoquer une méthode

En utilisant la réflexion, une méthode d'un objet peut être appelée pendant l'exécution.

L'exemple montre comment appeler les méthodes d'un objet String .

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

String s = "Hello World!";

// method without parameters
// invoke s.length()
Method method1 = String.class.getMethod("length");
int length = (int) method1.invoke(s); // variable length contains "12"

// method with parameters
// invoke s.substring(6)
Method method2 = String.class.getMethod("substring", int.class);
String substring = (String) method2.invoke(s, 6); // variable substring contains "World!"

Champs Obtenir et définir

À l'aide de l'API Reflection, il est possible de modifier ou d'obtenir la valeur d'un champ à l'exécution. Par exemple, vous pouvez l'utiliser dans une API pour extraire différents champs basés sur un facteur, comme le système d'exploitation. Vous pouvez également supprimer des modificateurs comme final pour permettre la modification des champs qui sont finaux.

Pour ce faire, vous devrez utiliser la méthode Class # getField () de la manière indiquée ci-dessous:

// Get the field in class SomeClass "NAME".
Field nameField = SomeClass.class.getDeclaredField("NAME");

// Get the field in class Field "modifiers". Note that it does not 
// need to be static
Field modifiersField = Field.class.getDeclaredField("modifiers");

// Allow access from anyone even if it's declared private
modifiersField.setAccessible(true);

// Get the modifiers on the "NAME" field as an int.
int existingModifiersOnNameField = nameField.getModifiers();

// Bitwise AND NOT Modifier.FINAL (16) on the existing modifiers
// Readup here https://en.wikipedia.org/wiki/Bitwise_operations_in_C
// if you're unsure what bitwise operations are.
int newModifiersOnNameField = existingModifiersOnNameField & ~Modifier.FINAL;

// Set the value of the modifiers field under an object for non-static fields
modifiersField.setInt(nameField, newModifiersOnNameField);

// Set it to be accessible. This overrides normal Java 
// private/protected/package/etc access control checks.
nameField.setAccessible(true);

 // Set the value of "NAME" here. Note the null argument. 
 // Pass null when modifying static fields, as there is no instance object
nameField.set(null, "Hacked by reflection...");

// Here I can directly access it. If needed, use reflection to get it. (Below)
System.out.println(SomeClass.NAME);

Obtenir des champs est beaucoup plus facile. Nous pouvons utiliser Field # get () et ses variantes pour obtenir sa valeur:

// Get the field in class SomeClass "NAME".
Field nameField = SomeClass.class.getDeclaredField("NAME");

// Set accessible for private fields
nameField.setAccessible(true);

// Pass null as there is no instance, remember?
String name = (String) nameField.get(null);

Notez ceci:

Lorsque vous utilisez Class # getDeclaredField , utilisez-le pour obtenir un champ dans la classe elle-même:

class HackMe extends Hacked {
    public String iAmDeclared;
}

class Hacked {
    public String someState;
}

Ici, HackMe#iAmDeclared est déclaré champ. Cependant, HackMe#someState n'est pas un champ déclaré car il est hérité de sa super-classe, Hacked.

Appel constructeur

Obtenir l'objet constructeur

Vous pouvez obtenir la classe Constructor partir de l'objet Class comme ceci:

Class myClass = ... // get a class object
Constructor[] constructors = myClass.getConstructors();

Où la variable constructors aura une instance de Constructor pour chaque constructeur public déclaré dans la classe.

Si vous connaissez les types de paramètres précis du constructeur auquel vous souhaitez accéder, vous pouvez filtrer le constructeur spécifique. L'exemple suivant retourne le constructeur public de la classe donnée qui prend un Integer comme paramètre:

Class myClass = ... // get a class object
Constructor constructor = myClass.getConstructor(new Class[]{Integer.class});

Si aucun constructeur ne correspond aux arguments du constructeur, une NoSuchMethodException est lancée.

Nouvelle instance utilisant un objet constructeur

Class myClass = MyObj.class // get a class object
Constructor constructor = myClass.getConstructor(Integer.class);
MyObj myObj = (MyObj) constructor.newInstance(Integer.valueOf(123));

Obtenir les constantes d'une énumération

Donner cette énumération comme exemple:

enum Compass {
    NORTH(0),
    EAST(90),
    SOUTH(180),
    WEST(270);
    private int degree;
    Compass(int deg){
        degree = deg;
    }
    public int getDegree(){
        return degree;
    }
}

En Java, une classe enum est comme toute autre classe mais possède des constantes définies pour les valeurs enum. De plus, il contient un champ qui est un tableau contenant toutes les valeurs et deux méthodes statiques avec des values() name values() et valueOf(String) .
Nous pouvons le voir si nous utilisons Reflection pour imprimer tous les champs de cette classe

for(Field f : Compass.class.getDeclaredFields())
    System.out.println(f.getName());

le résultat sera:

NORD
EST
SUD
OUEST
degré
ENUM $ VALUES

Nous pourrions donc examiner les classes enum avec Reflection comme toute autre classe. Mais l'API Reflection propose trois méthodes spécifiques à l'énumération.

chèque enum

Compass.class.isEnum();

Renvoie true pour les classes qui représentent un type enum.

récupérer des valeurs

Object[] values = Compass.class.getEnumConstants();

Retourne un tableau de toutes les valeurs enum comme Compass.values ​​() mais sans avoir besoin d'une instance.

enum contrôle constant

for(Field f : Compass.class.getDeclaredFields()){
    if(f.isEnumConstant())
        System.out.println(f.getName());
}

Répertorie tous les champs de classe qui sont des valeurs enum.

Obtenir la classe en fonction de son nom (entièrement qualifié)

Étant donné une String contenant le nom d'une classe, son objet Class est accessible à l'aide de Class.forName :

Class clazz = null;
try {
    clazz = Class.forName("java.lang.Integer");
} catch (ClassNotFoundException ex) {
    throw new IllegalStateException(ex);
}
Java SE 1.2

Il peut être spécifié si la classe doit être initialisée (deuxième paramètre de forName ) et quel ClassLoader doit être utilisé (troisième paramètre):

ClassLoader classLoader = ...
boolean initialize = ...
Class clazz = null;
try {
    clazz = Class.forName("java.lang.Integer", initialize, classLoader);
} catch (ClassNotFoundException ex) {
    throw new IllegalStateException(ex);
}

Appeler les constructeurs surchargés en utilisant la réflexion

Exemple: appel de différents constructeurs en passant des paramètres pertinents

import java.lang.reflect.*;

class NewInstanceWithReflection{
    public NewInstanceWithReflection(){
        System.out.println("Default constructor");
    }
    public NewInstanceWithReflection( String a){
        System.out.println("Constructor :String => "+a);
    }
    public static void main(String args[]) throws Exception {
        
        NewInstanceWithReflection object = (NewInstanceWithReflection)Class.forName("NewInstanceWithReflection").newInstance();
        Constructor constructor = NewInstanceWithReflection.class.getDeclaredConstructor( new Class[] {String.class});
        NewInstanceWithReflection object1 = (NewInstanceWithReflection)constructor.newInstance(new Object[]{"StackOverFlow"});
        
    }
}

sortie:

Default constructor
Constructor :String => StackOverFlow

Explication:

  1. Créer une instance de classe en utilisant Class.forName : elle appelle le constructeur par défaut
  2. Appelez getDeclaredConstructor de la classe en transmettant le type de paramètres en tant que Class array
  3. Après avoir obtenu le constructeur, créez newInstance en transmettant la valeur du paramètre en tant que Object array

Mauvaise utilisation de l'API Reflection pour modifier les variables privées et finales

La réflexion est utile lorsqu'elle est correctement utilisée à bon escient. En utilisant la réflexion, vous pouvez accéder aux variables privées et réinitialiser les variables finales.

Voici l'extrait de code, qui n'est pas recommandé.

import java.lang.reflect.*;

public class ReflectionDemo{
    public static void main(String args[]){
        try{
            Field[] fields = A.class.getDeclaredFields();
            A a = new A();
            for ( Field field:fields ) {
                if(field.getName().equalsIgnoreCase("name")){
                    field.setAccessible(true);
                    field.set(a, "StackOverFlow");
                    System.out.println("A.name="+field.get(a));
                }
                if(field.getName().equalsIgnoreCase("age")){
                    field.set(a, 20);
                    System.out.println("A.age="+field.get(a));
                }
                if(field.getName().equalsIgnoreCase("rep")){
                    field.setAccessible(true);
                    field.set(a,"New Reputation");
                    System.out.println("A.rep="+field.get(a));
                }
                if(field.getName().equalsIgnoreCase("count")){
                    field.set(a,25);
                    System.out.println("A.count="+field.get(a));
                }
            }                
        }catch(Exception err){
            err.printStackTrace();
        }
    }
}

class A {
    private String name;
    public int age;
    public final String rep;
    public static int count=0;
    
    public A(){
        name = "Unset";
        age = 0;
        rep = "Reputation";
        count++;
    }
}

Sortie:

A.name=StackOverFlow
A.age=20
A.rep=New Reputation
A.count=25

Explication:

Dans un scénario normal, private variables private ne sont pas accessibles en dehors de la classe déclarée (sans les méthodes getter et setter). final variables final ne peuvent pas être réaffectées après l'initialisation.

Reflection brise les deux obstacles peut être utilisée pour modifier les variables privées et finales comme expliqué ci-dessus.

field.setAccessible(true) est la clé pour obtenir la fonctionnalité souhaitée.

Appelez le constructeur de la classe imbriquée

Si vous souhaitez créer une instance d'une classe imbriquée interne, vous devez fournir un objet de classe de la classe englobante en tant que paramètre supplémentaire avec Class # getDeclaredConstructor .

public class Enclosing{
    public class Nested{
    public Nested(String a){
            System.out.println("Constructor :String => "+a);
        }
    }       
    public static void main(String args[]) throws Exception {
        Class<?> clazzEnclosing = Class.forName("Enclosing");            
        Class<?> clazzNested = Class.forName("Enclosing$Nested");
        Enclosing objEnclosing = (Enclosing)clazzEnclosing.newInstance();
        Constructor<?> constructor = clazzNested.getDeclaredConstructor(new Class[]{Enclosing.class, String.class});
        Nested objInner = (Nested)constructor.newInstance(new Object[]{objEnclosing, "StackOverFlow"});
    }
}

Si la classe imbriquée est statique, vous n'aurez pas besoin de cette instance englobante.

Proxies dynamiques

Les proxys dynamiques n'ont pas grand chose à voir avec Reflection, mais ils font partie de l'API. C'est essentiellement un moyen de créer une implémentation dynamique d'une interface. Cela peut être utile lors de la création de services de maquette.
Un proxy dynamique est une instance d'une interface créée avec un gestionnaire d'invocation appelé qui intercepte tous les appels de méthode et permet la gestion manuelle de leur appel.

public class DynamicProxyTest {

    public interface MyInterface1{
        public void someMethod1();
        public int someMethod2(String s);
    }

    public interface MyInterface2{
        public void anotherMethod();
    }
   
    public static void main(String args[]) throws Exception {
        // the dynamic proxy class 
        Class<?> proxyClass = Proxy.getProxyClass(
                ClassLoader.getSystemClassLoader(),
                new Class[] {MyInterface1.class, MyInterface2.class});
        // the dynamic proxy class constructor
        Constructor<?> proxyConstructor = 
            proxyClass.getConstructor(InvocationHandler.class);

        // the invocation handler
        InvocationHandler handler = new InvocationHandler(){
            // this method is invoked for every proxy method call
            // method is the invoked method, args holds the method parameters
            // it must return the method result
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
                String methodName = method.getName();

                if(methodName.equals("someMethod1")){
                    System.out.println("someMethod1 was invoked!");
                    return null;
                }
                if(methodName.equals("someMethod2")){
                    System.out.println("someMethod2 was invoked!");
                    System.out.println("Parameter: " + args[0]);
                    return 42;
                }
                if(methodName.equals("anotherMethod")){
                    System.out.println("anotherMethod was invoked!");
                    return null;
                }
                System.out.println("Unkown method!");
                return null;
            }
        };

        // create the dynamic proxy instances
        MyInterface1 i1 = (MyInterface1) proxyConstructor.newInstance(handler);
        MyInterface2 i2 = (MyInterface2) proxyConstructor.newInstance(handler);

        // and invoke some methods
        i1.someMethod1();
        i1.someMethod2("stackoverflow");
        i2.anotherMethod();
    }
}

Le résultat de ce code est le suivant:

someMethod1 was invoked!
someMethod2 was invoked!
Parameter: stackoverflow
anotherMethod was invoked!

Mal Java hacks avec réflexion

L'API de réflexion peut être utilisée pour modifier les valeurs des champs privé et final, même dans la bibliothèque par défaut du JDK. Cela pourrait être utilisé pour manipuler le comportement de certaines classes bien connues comme nous le verrons.

Ce qui n'est pas possible

Laisser commencer par la seule limitation signifie que le seul champ que nous ne pouvons pas modifier avec Reflection. C'est le Java SecurityManager . Il est déclaré dans java.lang.System comme

private static volatile SecurityManager security = null;

Mais il ne sera pas répertorié dans la classe System si nous exécutons ce code

for(Field f : System.class.getDeclaredFields())
    System.out.println(f);

C'est à cause de fieldFilterMap dans sun.reflect.Reflection qui contient la carte elle-même et le champ de sécurité dans System.class et les protège contre tout accès avec Reflection. Nous n'avons donc pas pu désactiver le SecurityManager .

Cordes folles

Chaque chaîne Java est représentée par la machine virtuelle Java en tant qu'instance de la classe String . Toutefois, dans certaines situations, la machine virtuelle Java enregistre l'espace mémoire en utilisant la même instance pour les chaînes qui le sont. Cela se produit pour les littéraux de chaîne, ainsi que pour les chaînes qui ont été "internées" en appelant String.intern() . Donc, si vous avez "hello" dans votre code plusieurs fois, c'est toujours la même instance d'objet.

Les chaînes sont supposées être immuables, mais il est possible d'utiliser une réflexion "mauvaise" pour les changer. L'exemple ci-dessous montre comment nous pouvons modifier les caractères d'une chaîne en remplaçant son champ de value .

public class CrazyStrings {
    static {
        try {
            Field f = String.class.getDeclaredField("value");
            f.setAccessible(true);
            f.set("hello", "you stink!".toCharArray());
        } catch (Exception e) {
        }
    }
    public static void main(String args[])  {
        System.out.println("hello");
    }
}

Donc, ce code imprimera "vous pue!"

1 = 42

La même idée pourrait être utilisée avec la classe Integer

public class CrazyMath {
    static {
        try {
            Field value = Integer.class.getDeclaredField("value");    
            value.setAccessible(true);          
            value.setInt(Integer.valueOf(1), 42);      
        } catch (Exception e) {
        }
    }
    public static void main(String args[])  {
        System.out.println(Integer.valueOf(1));
    }
}

Tout est vrai

Et selon ce post stackoverflow, nous pouvons utiliser la réflexion pour faire quelque chose de vraiment mal.

public class Evil {    
    static {
        try {
            Field field = Boolean.class.getField("FALSE");
            field.setAccessible(true);
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
            field.set(null, true);
        } catch (Exception e) {
        }
    }
    public static void main(String args[]){
        System.out.format("Everything is %s", false);
    }
}

Notez que ce que nous faisons ici va provoquer un comportement inexplicable de la JVM. C'est très dangereux.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow