JavaRush /Java-Blog /Random-DE /Reflection-API. Betrachtung. Die dunkle Seite von Java
Oleksandr Klymenko
Level 13
Харків

Reflection-API. Betrachtung. Die dunkle Seite von Java

Veröffentlicht in der Gruppe Random-DE
Grüße, junger Padawan. In diesem Artikel erzähle ich Ihnen von der Macht, deren Kraft Java-Programmierer nur in scheinbar aussichtslosen Situationen nutzen. Die dunkle Seite von Java ist also –Reflection API
Reflection-API.  Betrachtung.  Die dunkle Seite von Java – 1
Die Reflexion in Java erfolgt mithilfe der Java Reflection API. Was ist diese Reflexion? Es gibt eine kurze und präzise Definition, die auch im Internet beliebt ist. Reflexion (von spätlateinisch reflexio – zurückgehen) ist ein Mechanismus zur Untersuchung von Daten über ein Programm während seiner Ausführung. Mit Reflection können Sie Informationen zu Feldern, Methoden und Klassenkonstruktoren untersuchen. Der Reflexionsmechanismus selbst ermöglicht es Ihnen, Typen zu verarbeiten, die während der Kompilierung fehlen, aber während der Programmausführung erscheinen. Reflexion und das Vorhandensein eines logisch kohärenten Modells zur Fehlerberichterstattung ermöglichen die Erstellung korrekten dynamischen Codes. Mit anderen Worten: Wenn Sie verstehen, wie Reflexion in Java funktioniert, eröffnen sich Ihnen eine Reihe erstaunlicher Möglichkeiten. Sie können Klassen und ihre Komponenten buchstäblich unter einen Hut bringen.
Reflection-API.  Betrachtung.  Die dunkle Seite von Java – 2
Hier ist eine grundlegende Liste dessen, was Reflexion ermöglicht:
  • Die Klasse eines Objekts herausfinden/bestimmen;
  • Informieren Sie sich über Klassenmodifikatoren, Felder, Methoden, Konstanten, Konstruktoren und Superklassen.
  • Finden Sie heraus, welche Methoden zur implementierten Schnittstelle/Schnittstellen gehören;
  • Erstellen Sie eine Instanz einer Klasse. Der Name der Klasse ist unbekannt, bis das Programm ausgeführt wird.
  • Rufen Sie den Wert eines Objektfelds anhand des Namens ab und legen Sie ihn fest.
  • Rufen Sie die Methode eines Objekts nach Namen auf.
Reflection wird in fast allen modernen Java-Technologien verwendet. Es ist schwer vorstellbar, ob Java als Plattform ohne Nachdenken eine solch enorme Verbreitung hätte erreichen können. Höchstwahrscheinlich konnte ich es nicht. Sie haben sich mit dem allgemeinen theoretischen Gedanken der Reflexion vertraut gemacht, nun kommen wir zur praktischen Anwendung! Wir werden nicht alle Methoden der Reflection API untersuchen, sondern nur das, was tatsächlich in der Praxis vorkommt. Da der Reflexionsmechanismus die Arbeit mit Klassen beinhaltet, haben wir eine einfache Klasse MyClass:
public class MyClass {
   private int number;
   private String name = "default";
//    public MyClass(int number, String name) {
//        this.number = number;
//        this.name = name;
//    }
   public int getNumber() {
       return number;
   }
   public void setNumber(int number) {
       this.number = number;
   }
   public void setName(String name) {
       this.name = name;
   }
   private void printData(){
       System.out.println(number + name);
   }
}
Wie wir sehen können, ist dies die häufigste Klasse. Der Konstruktor mit Parametern ist aus einem bestimmten Grund auskommentiert, wir werden später darauf zurückkommen. Wenn Sie sich den Inhalt des Kurses genau angesehen haben, ist Ihnen wahrscheinlich das Fehlen von getter„a“ für das aufgefallen name. Das Feld selbst nameist mit einem Zugriffsmodifikator markiert private; wir können außerhalb der Klasse selbst nicht darauf zugreifen; =>wir können seinen Wert nicht abrufen. "Also, was ist das Problem? - du sagst. „Fügen Sie getterden Zugriffsmodifikator hinzu oder ändern Sie ihn.“ Und Sie werden Recht haben, aber was ist, wenn MyClasses sich in einer kompilierten AAR-Bibliothek oder in einem anderen geschlossenen Modul ohne Bearbeitungszugriff befindet, und in der Praxis kommt dies äußerst häufig vor? Und irgendein unaufmerksamer Programmierer hat einfach vergessen zu schreiben getter. Es ist Zeit, sich an die Reflexion zu erinnern! Versuchen wir, zum Klassenfeld privatezu gelangen : nameMyClass
public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; //no getter =(
   System.out.println(number + name);//output 0null
   try {
       Field field = myClass.getClass().getDeclaredField("name");
       field.setAccessible(true);
       name = (String) field.get(myClass);
   } catch (NoSuchFieldException | IllegalAccessException e) {
       e.printStackTrace();
   }
   System.out.println(number + name);//output 0default
}
Lassen Sie uns jetzt herausfinden, was hier passiert ist. Es gibt eine wunderbare Klasse in Java Class. Es repräsentiert Klassen und Schnittstellen in einer ausführbaren Java-Anwendung. Auf den Zusammenhang zwischen Classund gehen wir nicht näher ein ClassLoader. Dies ist nicht das Thema des Artikels. Um die Felder dieser Klasse abzurufen, müssen Sie als Nächstes die Methode aufrufen getFields(). Diese Methode gibt uns alle verfügbaren Felder der Klasse zurück. Dies ist für uns nicht geeignet, da unser Feld ist private, also verwenden wir die Methode getDeclaredFields(). Diese Methode gibt ebenfalls ein Array von Klassenfeldern zurück, aber jetzt sowohl privateund protected. In unserer Situation kennen wir den Namen des Feldes, das uns interessiert, und können die Methode verwenden getDeclaredField(String), wobei Stringder Name des gewünschten Felds steht. Notiz: getFields()und getDeclaredFields()geben Sie nicht die Felder der übergeordneten Klasse zurück! Super, wir haben ein Objekt Field mit einem Link zu unserem erhalten name. Weil Das Feld war nicht публичным(öffentlich), es sollte Zugang zum Arbeiten damit gewährt werden. Die Methode setAccessible(true)ermöglicht es uns, weiterzuarbeiten. Jetzt ist das Feld namevollständig unter unserer Kontrolle! Sie können seinen Wert erhalten, indem Sie get(Object)das Objekt aufrufen Field, bei dem Objectes sich um eine Instanz unserer Klasse handelt MyClass. Wir wandeln es um Stringund weisen es unserer Variablen zu name. Für den Fall, dass wir 'a plötzlich nicht mehr haben setter, können wir mit der Methode einen neuen Wert für das Namensfeld festlegen set:
field.set(myClass, (String) "new value");
Glückwunsch! Sie haben gerade den grundlegenden Mechanismus der Reflexion gemeistert und konnten auf das privateFeld zugreifen! Achten Sie auf den Block try/catchund die Art der behandelten Ausnahmen. Die IDE selbst wird auf ihre obligatorische Anwesenheit hinweisen, aber ihr Name macht deutlich, warum sie hier sind. Fortfahren! Wie Sie vielleicht bemerkt haben, MyClassverfügt unsere bereits über eine Methode zum Anzeigen von Informationen zu den Klassendaten:
private void printData(){
       System.out.println(number + name);
   }
Aber auch hier hat dieser Programmierer ein Vermächtnis hinterlassen. Die Methode steht unter dem Zugriffsmodifikator privateund wir mussten den Ausgabecode jedes Mal selbst schreiben. Es ist nicht in Ordnung, wo ist unser Spiegelbild? ... Schreiben wir die folgende Funktion:
public static void printData(Object myClass){
   try {
       Method method = myClass.getClass().getDeclaredMethod("printData");
       method.setAccessible(true);
       method.invoke(myClass);
   } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
       e.printStackTrace();
   }
}
Hier ist die Vorgehensweise ungefähr die gleiche wie beim Abrufen eines Felds – wir holen uns die gewünschte Methode anhand des Namens und gewähren Zugriff darauf. Und um das Objekt aufzurufen, Methoddas wir verwenden invoke(Оbject, Args), Оbjectbefindet sich dort auch eine Instanz der Klasse MyClass. Args- Methodenargumente – bei uns gibt es keine. Jetzt verwenden wir die Funktion, um Informationen anzuzeigen printData:
public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; //?
   printData(myClass); // outout 0default
   try {
       Field field = myClass.getClass().getDeclaredField("name");
       field.setAccessible(true);
       field.set(myClass, (String) "new value");
       name = (String) field.get(myClass);
   } catch (NoSuchFieldException | IllegalAccessException e) {
       e.printStackTrace();
   }
   printData(myClass);// output 0new value
}
Hurra, jetzt haben wir Zugriff auf die private Methode der Klasse. Aber was ist, wenn die Methode noch Argumente hat und warum es einen auskommentierten Konstruktor gibt? Alles zu seiner Zeit. Aus der Definition am Anfang geht hervor, dass Sie mit Reflection Instanzen einer Klasse in einem Modus erstellen können runtime(während das Programm ausgeführt wird)! Wir können ein Objekt einer Klasse anhand des vollständig qualifizierten Namens dieser Klasse erstellen. Der vollständig qualifizierte Klassenname ist der Name der Klasse unter Angabe des Pfads zu ihr in package.
Reflection-API.  Betrachtung.  Die dunkle Seite von Java – 3
In meiner Hierarchie lautet packageder vollständige Name „ “. Sie können den Klassennamen auch auf einfache Weise herausfinden (der Klassenname wird als Zeichenfolge zurückgegeben): MyClassreflection.MyClass
MyClass.class.getName()
Lassen Sie uns mithilfe von Reflektion eine Instanz der Klasse erstellen:
public static void main(String[] args) {
   MyClass myClass = null;
   try {
       Class clazz = Class.forName(MyClass.class.getName());
       myClass = (MyClass) clazz.newInstance();
   } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
       e.printStackTrace();
   }
   System.out.println(myClass);//output created object reflection.MyClass@60e53b93
}
Zum Zeitpunkt des Starts der Java-Anwendung sind nicht alle Klassen in die JVM geladen. Wenn Ihr Code nicht auf die Klasse verweist MyClass, wird die Person, die für das Laden der Klassen in die JVM verantwortlich ist, und das heißt ClassLoader, sie niemals dort laden. Daher müssen wir ClassLoaderes zwingen, eine Beschreibung unserer Klasse in Form einer Variablen vom Typ zu laden und zu empfangen Class. Für diese Aufgabe gibt es eine Methode forName(String), in der Stringder Name der Klasse steht, deren Beschreibung wir benötigen. Nach Erhalt wird Сlassder Methodenaufruf newInstance()zurückgegeben Object, der gemäß der gleichen Beschreibung erstellt wird. Es bleibt, dieses Objekt in unsere Klasse zu bringen MyClass. Cool! Es war schwierig, aber ich hoffe, es ist verständlich. Jetzt können wir eine Instanz einer Klasse buchstäblich aus einer Zeile erstellen! Leider funktioniert die beschriebene Methode nur mit dem Standardkonstruktor (ohne Parameter). Wie rufe ich Methoden mit Argumenten und Konstruktoren mit Parametern auf? Es ist Zeit, den Kommentar unseres Konstruktors zu entfernen. Wie erwartet newInstance()findet es den Standardkonstruktor nicht und funktioniert nicht mehr. Schreiben wir die Erstellung einer Klasseninstanz neu:
public static void main(String[] args) {
   MyClass myClass = null;
   try {
       Class clazz = Class.forName(MyClass.class.getName());
       Class[] params = {int.class, String.class};
       myClass = (MyClass) clazz.getConstructor(params).newInstance(1, "default2");
   } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
       e.printStackTrace();
   }
   System.out.println(myClass);//output created object reflection.MyClass@60e53b93
}
Um Klassenkonstruktoren zu erhalten, rufen Sie die Methode aus der Klassenbeschreibung auf getConstructors(), und um Konstruktorparameter zu erhalten, rufen Sie Folgendes auf getParameterTypes():
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
   Class[] paramTypes = constructor.getParameterTypes();
   for (Class paramType : paramTypes) {
       System.out.print(paramType.getName() + " ");
   }
   System.out.println();
}
Auf diese Weise erhalten wir alle Konstruktoren und alle Parameter dazu. In meinem Beispiel erfolgt ein Aufruf eines bestimmten Konstruktors mit bestimmten, bereits bekannten Parametern. Und um diesen Konstruktor aufzurufen, verwenden wir die Methode newInstance, in der wir die Werte dieser Parameter angeben. Das Gleiche gilt invokefür Aufrufmethoden. Es stellt sich die Frage: Wo kann ein reflexiver Aufruf von Konstruktoren sinnvoll sein? Moderne Java-Technologien kommen, wie eingangs erwähnt, nicht ohne die Reflection API aus. Zum Beispiel DI (Dependency Injection), bei dem Annotationen in Kombination mit der Reflexion von Methoden und Konstruktoren die in der Android-Entwicklung beliebte Dagger-Bibliothek bilden. Nachdem Sie diesen Artikel gelesen haben, können Sie davon ausgehen, dass Sie mit den Mechanismen der Reflection API vertraut sind. Nicht umsonst wird Reflexion als die dunkle Seite von Java bezeichnet. Es bricht das OOP-Paradigma vollständig. In Java dient die Kapselung dazu, einige Programmkomponenten zu verbergen und den Zugriff auf andere zu beschränken. Mit der Verwendung des privaten Modifikators meinen wir, dass der Zugriff auf dieses Feld nur innerhalb der Klasse erfolgt, in der dieses Feld vorhanden ist. Auf dieser Grundlage erstellen wir die weitere Architektur des Programms. In diesem Artikel haben wir gesehen, wie Sie Reflexion nutzen können, um irgendwohin zu gelangen. Ein gutes Beispiel in Form einer architektonischen Lösung ist das generative Designmuster – Singleton. Die Grundidee besteht darin, dass die Klasse, die diese Vorlage implementiert, während des gesamten Programmbetriebs nur eine Kopie haben sollte. Dies geschieht, indem der Standardzugriffsmodifikator für den Konstruktor auf „privat“ gesetzt wird. Und es wird sehr schlimm sein, wenn ein Programmierer mit seiner eigenen Reflexion solche Klassen erstellt. Übrigens gibt es eine sehr interessante Frage, die ich kürzlich von meinem Mitarbeiter gehört habe: Kann eine Klasse, die eine Vorlage implementiert, SingletonErben haben? Ist es möglich, dass selbst die Reflexion in diesem Fall machtlos ist? Schreiben Sie Ihr Feedback zum Artikel und der Antwort in die Kommentare und stellen Sie auch Ihre Fragen! Die wahre Stärke der Reflection API liegt in der Kombination mit Runtime Annotations, über die wir wahrscheinlich in einem zukünftigen Artikel über die Schattenseiten von Java sprechen werden. Vielen Dank für Ihre Aufmerksamkeit!
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION