Suche…


Einführung

Reflection wird häufig von Programmen verwendet, bei denen das Laufzeitverhalten von Anwendungen, die in der JVM ausgeführt werden, untersucht oder geändert werden muss. Die Java Reflection-API wird zu diesem Zweck verwendet, um Klassen, Schnittstellen, Felder und Methoden zur Laufzeit zu untersuchen, ohne deren Namen zur Kompilierzeit zu kennen. Außerdem können neue Objekte instanziiert und Methoden mithilfe von Reflektion aufgerufen werden.

Bemerkungen

Performance

Beachten Sie, dass Reflektionen die Leistung beeinträchtigen können. Verwenden Sie sie nur, wenn Ihre Aufgabe nicht ohne Reflektion abgeschlossen werden kann.

Aus dem Java-Tutorial The Reflection API :

Da bei der Reflektion dynamisch aufgelöste Typen beteiligt sind, können bestimmte Java Virtual Machine-Optimierungen nicht durchgeführt werden. Folglich haben reflektierende Operationen eine langsamere Leistung als ihre nicht reflektierenden Pendants und sollten in Codeabschnitten vermieden werden, die in leistungsempfindlichen Anwendungen häufig genannt werden.

Einführung

Grundlagen

Die Reflection-API ermöglicht die Überprüfung der Klassenstruktur des Codes zur Laufzeit und den dynamischen Aufruf von Code. Dies ist sehr mächtig, aber es ist auch gefährlich, da der Compiler nicht statisch feststellen kann, ob dynamische Aufrufe gültig sind.

Ein einfaches Beispiel wäre, die öffentlichen Konstruktoren und Methoden einer bestimmten Klasse abzurufen:

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

Mit diesen Informationen ist es möglich, das Objekt zu instanziieren und verschiedene Methoden dynamisch aufzurufen.

Reflexion und generische Typen

Generische Typinformationen sind verfügbar für:

  • Methodenparameter mit getGenericParameterTypes() .
  • Rückgabetypen für Methoden mithilfe von getGenericReturnType() .
  • öffentliche Felder mit getGenericType .

Das folgende Beispiel zeigt, wie die generischen Typinformationen in allen drei Fällen extrahiert werden:

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;
    }

}

Daraus ergibt sich folgende Ausgabe:

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

Methode aufrufen

Mit Reflection kann eine Methode eines Objekts zur Laufzeit aufgerufen werden.

Das Beispiel zeigt, wie die Methoden eines String Objekts String werden.

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!"

Felder abrufen und einstellen

Mit der Reflection-API können Sie zur Laufzeit den Wert eines Felds ändern oder abrufen. Sie können es beispielsweise in einer API verwenden, um verschiedene Felder basierend auf einem Faktor wie dem Betriebssystem abzurufen. Sie können auch Modifikatoren wie final entfernen, um endgültige Felder zu ändern.

Dazu müssen Sie die Methode Class # getField () wie folgt verwenden :

// 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);

Felder zu bekommen ist viel einfacher. Wir können Field # get () und seine Varianten verwenden, um seinen Wert zu erhalten:

// 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);

Beachten Sie dies:

Wenn Sie die Klasse # getDeclaredField verwenden , verwenden Sie sie, um ein Feld in der Klasse selbst abzurufen :

class HackMe extends Hacked {
    public String iAmDeclared;
}

class Hacked {
    public String someState;
}

Hier wird HackMe#iAmDeclared als Feld deklariert. HackMe#someState ist jedoch kein deklariertes Feld, da es von seiner Superklasse Hacked geerbt wird.

Konstruktor aufrufen

Das Konstruktorobjekt abrufen

Sie können die Constructor Klasse wie Constructor vom Class Objekt abrufen:

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

Die constructors hat eine Constructor für jeden in der Klasse deklarierten öffentlichen Konstruktor.

Wenn Sie die genauen Parametertypen des Konstruktors kennen, auf den Sie zugreifen möchten, können Sie den spezifischen Konstruktor filtern. Das nächste Beispiel gibt den öffentlichen Konstruktor der angegebenen Klasse zurück, der einen Integer Parameter als Parameter annimmt:

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

Wenn kein Konstruktor mit den angegebenen Konstruktorargumenten NoSuchMethodException wird eine NoSuchMethodException ausgelöst.

Neue Instanz mit Konstruktorobjekt

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

Die Konstanten einer Aufzählung erhalten

Diese Aufzählung als Beispiel geben:

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

In Java ist eine Enumenklasse wie jede andere Klasse, hat jedoch bestimmte Konstanten für die Enummenwerte. Darüber hinaus verfügt es über ein Feld, das ein Array mit allen Werten und zwei statischen Methoden mit dem Namen valueOf(String) values() und valueOf(String) .
Wir können dies sehen, wenn wir mit Reflection alle Felder in dieser Klasse drucken

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

Die Ausgabe wird sein:

NORDEN
OSTEN
SÜDEN
WEST
Grad
ENUM $ WERTE

So könnten wir Enum-Klassen wie jede andere Klasse mit Reflection untersuchen. Die Reflection-API bietet jedoch drei aufzählungsspezifische Methoden.

Aufzählungsprüfung

Compass.class.isEnum();

Gibt true für Klassen zurück, die einen Aufzählungstyp darstellen.

Werte abrufen

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

Gibt ein Array aller Aufzählungswerte wie Compass.values ​​() zurück, ohne dass eine Instanz erforderlich ist.

Enum ständige Prüfung

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

Listet alle Klassenfelder auf, die Listenwerte sind.

Bekommen Sie der Klasse den (vollständig qualifizierten) Namen

Bei einem String den Namen einer Klasse enthält, ist es Class - Objekt zugegriffen werden kann , mit Class.forName :

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

Es kann angegeben werden, ob die Klasse initialisiert werden soll (zweiter Parameter von forName ) und welcher ClassLoader werden soll (dritter Parameter):

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

Rufen Sie überladene Konstruktoren mit Reflektion auf

Beispiel: Rufen Sie verschiedene Konstruktoren auf, indem Sie relevante Parameter übergeben

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"});
        
    }
}

Ausgabe:

Default constructor
Constructor :String => StackOverFlow

Erläuterung:

  1. Instanz der Klasse mit Class.forName : Der Standardkonstruktor wird Class.forName
  2. Rufen Sie getDeclaredConstructor der Klasse auf, indem Sie den Typ der Parameter als Class array
  3. Erstellen newInstance nach dem newInstance des Konstruktors newInstance indem Sie den Parameterwert als Object array

Missbrauch der Reflection-API zum Ändern von privaten und endgültigen Variablen

Reflexion ist nützlich, wenn sie richtig für den richtigen Zweck verwendet wird. Mit Reflektion können Sie auf private Variablen zugreifen und endgültige Variablen erneut initialisieren.

Unten ist der Code-Ausschnitt, der nicht empfohlen wird.

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++;
    }
}

Ausgabe:

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

Erläuterung:

Im normalen Szenario kann nicht auf private Variablen außerhalb der deklarierten Klasse (ohne Getter- und Setter-Methoden) zugegriffen werden. final Variablen können nach der Initialisierung nicht neu zugeordnet werden.

Reflection durchbricht beide Barrieren, um private und letzte Variablen zu ändern, wie oben erläutert.

field.setAccessible(true) ist der Schlüssel zum Erreichen der gewünschten Funktionalität.

Rufen Sie den Konstruktor der verschachtelten Klasse auf

Wenn Sie eine Instanz einer inneren verschachtelten Klasse erstellen möchten, müssen Sie ein Klassenobjekt der umgebenden Klasse als zusätzlichen Parameter mit Class # getDeclaredConstructor angeben .

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"});
    }
}

Wenn die verschachtelte Klasse statisch ist, benötigen Sie diese einschließende Instanz nicht.

Dynamische Proxies

Dynamische Proxies haben nicht wirklich viel mit Reflection zu tun, aber sie sind Teil der API. Es ist im Grunde eine Möglichkeit, eine dynamische Implementierung einer Schnittstelle zu erstellen. Dies kann beim Erstellen von Mockup-Services hilfreich sein.
Ein dynamischer Proxy ist eine Instanz einer Schnittstelle, die mit einem sogenannten Aufrufhandler erstellt wird, der alle Methodenaufrufe abfängt und deren manuellen Aufruf ermöglicht.

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();
    }
}

Das Ergebnis dieses Codes ist folgendes:

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

Böse Java-Hacks mit Reflection

Mit der Reflection-API können Sie auch in der JDK-Standardbibliothek Werte von privaten und endgültigen Feldern ändern. Dies kann verwendet werden, um das Verhalten einiger bekannter Klassen zu manipulieren, wie wir sehen werden.

Was ist nicht möglich

Beginnen wir mit der einzigen Einschränkung. Das einzige Feld, das wir mit Reflection nicht ändern können. Das ist der Java SecurityManager . Es ist in java.lang.System als deklariert

private static volatile SecurityManager security = null;

Es wird jedoch nicht in der Systemklasse aufgeführt, wenn wir diesen Code ausführen

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

Das liegt an der fieldFilterMap in sun.reflect.Reflection , die die Karte selbst und das Sicherheitsfeld in der System.class und sie vor dem Zugriff mit Reflection schützt. Daher konnten wir den SecurityManager nicht deaktivieren.

Verrückte Saiten

Jeder Java-String wird von der JVM als Instanz der String Klasse dargestellt. In einigen Situationen spart die JVM jedoch Heapspeicherplatz, indem dieselbe Instanz für Strings verwendet wird. Dies geschieht für String-Literale und auch für Strings, die durch Aufruf von String.intern() "interniert" wurden. Wenn Sie also mehrmals "hello" in Ihrem Code haben, handelt es sich immer um dieselbe Objektinstanz.

Strings sollen unveränderlich sein, aber es ist möglich, "böse" Reflexion zu verwenden, um sie zu ändern. Das folgende Beispiel zeigt , wie wir die Zeichen in einem String ändern kann durch seine Ersetzung value Feld.

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");
    }
}

Also wird dieser Code "Sie stinken!"

1 = 42

Die gleiche Idee könnte mit der Integer-Klasse verwendet werden

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));
    }
}

Alles ist wahr

Und gemäß diesem Stackoverflow-Beitrag können wir durch Nachdenken etwas wirklich Böses tun.

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);
    }
}

Beachten Sie, dass das, was wir hier tun, dazu führt, dass sich die JVM auf unerklärliche Weise verhält. Das ist sehr gefährlich.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow