16.08.2012
Zugriff auf private Schnittstellen von Klassen mit Reflection
Hier wird gezeigt, wie man mit Java Reflection auf private Methoden, private Membervariablen, private Konstruktoren und private innere Klassen und deren Funktionen zugreift.
Die Idee zu diesem "Unsinn" kam mir vor ein paar Jahren in der Mittagspause.
Irgend jemand wollte, dass alle public
-Methoden mit Javadoc versehen werden, so dass die diversen Code-Styling-Tools (CheckStyle, ...) zufrieden sind. Also fragte ich mich, ob man nicht alle Methoden als private
deklarieren könnte, um sie nicht mit einer Dokumentation versehen zu müssen. - Man kann jetzt schon sagen, dass hätte problemlos funktioniert... ;-)
Zugriff auf private Membervariable
Es ist wirklich erstaunlich, wie weit man dabei mit Reflection kommt. Als erstes wird eine Klasse erstellt, die eine private Membervariable enthält (_text
).
public class ClassWithPrivateInterface { private String _text = "This is a private text"; }
Um den Zugriff auf diese Variable zu demonstrieren, legen wir eine zweite Klasse PrivateTester
mit einer main-Methode an. Dann instanzieren wir die Klasse ClassWithPrivateInterface
und fragen anschließend die Klasse Class
des Objects privateInterface
durch den Aufruf privateInterface.getClass()
ab. Mit dieser Klasse lassen sich die deklarierten Membervariablen (Field
) mittels getDeclaredField
bestimmen. Wenn man den Namen der Variable kennt, dann läßt sich das Field, wie in dem Beispiel, auch direkt bestimmen. Für das field
wird mit setAccessible(true)
die Sichtbarkeit umgeschaltet. Und nun kann der getter von field
mit dem Objekt privateInterface
aufgerufen werden und liefert den String
zurück.
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class PrivateTester { /** * Copyright Joern Hameister 16.08.2012 */ public static void main(String[] args) throws Exception { ClassWithPrivateInterface privateInterface = new ClassWithPrivateInterface(); // Access private member variables final Class c = privateInterface.getClass(); final java.lang.reflect.Field field = c.getDeclaredField("_text"); field.setAccessible(true); final String privateText = (String) field.get(privateInterface); System.out.println(privateText); }
Aufruf von privaten Methoden
Der nächste Schritt ist der Aufruf einer privaten Methode. Dazu ergänzen wir in der Klasse ClassWithPrivateInterface
folgende Funktion:
private void printText() { System.out.println("Text printed..."); }
Ziel ist es nun diese aufzurufen, so dass der Text ausgegeben wird. Dazu fügen wir folgende Zeilen in der Klasse PrivateTester
ein.
//Access private methods Method privateMethod = c.getDeclaredMethod("printText", null); privateMethod.setAccessible(true); privateMethod.invoke(privateInterface, null);
Bei der Klasse c
fragen wir die deklarierte Methode mit dem Namen printText
, die keine Parameter hat, ab und speichern sie in der Variable privateMethod
. Danach wird wieder die Sichtbarkeit mit setAccessible(true)
umgesetzt und die Methode mittels invoke
aufgerufen, so dass der Text Text printed...
auf der Konsole ausgegeben wird.
Aufruf von privaten Methoden mit Parameterübergabe
Die Parameterübergabe bei privaten Funktionen ist auch kein großes Problem. Als erstes wird eine neue Methode in der Klasse ClassWithPrivateInterface
hinzugefügt, die als Parameter einen String übergeben bekommt.
private void printTextWithParameter(String parameter) { System.out.println("Text printed with parameter "+parameter+"..."); }
Um diese private Methode aufzurufen, wird die Methode Method
mit getDeclaredMethod(String, Class)
bestimmt. (Weitere Parameter werden einfach mit Komma getrennt angefügt.). Nun wird die Sichtbarkeit der Methode mit setAccessible(true)
umgestellt und kann dann mit invoke
aufgerufen werden. Als Parameter wird das Objekt privateInterface
und der String
-Parameter übergeben.
//Access private methods with parameters Method privateMethodWithParameter = c.getDeclaredMethod("printTextWithParameter", String.class); privateMethodWithParameter.setAccessible(true); privateMethodWithParameter.invoke(privateInterface, "ParameterA");
Zugriff auf private innere Klassen (inner Classes
)
Auch der Zugriff auf private innere Klassen mit privaten Funktionen ist mit Reflection problemlos möglich. Um das zu demonstrieren, erweitern wird die Klasse ClassWithPrivateInterface
um eine private innere Klasse.
private class PrivateInnerClass { private void printInnerText() { System.out.println("Print text of private inner class"); } }
Damit sichergestellt ist, dass eine Instanz dieser Klasse existiert, fügen wir noch einen Konstruktur und eine Variablendeklaration hinzu.
private PrivateInnerClass _innerClass; public ClassWithPrivateMethods() { _innerClass = new PrivateInnerClass(); }
Nun wird beim Erzeugen einer Instanz ClassWithPrivateMethods
auch ein Objekt vom Type ClassWithPrivateMethods
erstellt.
Den Zugriff auf die private Funktion printInnerText
realisiert man folgendermaßen. Als erstes bestimmt man die Membervariable, wie weiter oben schon beschrieben. Anschließend werden von der Klasse c
alle inneren Klasse abgefragt. In dem Beispiel wird einfach, ohne weitere Prüfung, die erste Klasse mit dem Index 0 verwendet, und die Methode mit dem Namen printInnerText
abgefragt. Danach kann diese Methode aufgerufen werden und gibt den Text Print text of private inner class
auf der Konsole aus.
//Call private method of private inner class java.lang.reflect.Field innerClass = c.getDeclaredField("_innerClass"); innerClass.setAccessible(true); Object privateInnerClass = innerClass.get(privateInterface); Class[] classes = c.getDeclaredClasses(); Method privateMethodInPrivateClass = classes[0].getDeclaredMethod("printInnerText", null); privateMethodInPrivateClass.setAccessible(true); privateMethodInPrivateClass.invoke(privateInnerClass, null);
Aufruf von private Konstruktoren mit Parametern
Was jetzt noch fehlt, ist der Aufruf von privaten Konstruktoren, inklusive einer Parameterübergabe an den Konstruktor. Dazu wird widerum die Klasse ClassWithPrivateInterface
um einen Konstruktor mit einem Parameter vom Type String
erweitert.
private ClassWithPrivateInterface(String text) { System.out.println("Created object with private constructor and parameter: "+text); }
Als erstes wird der Konstruktor mit Hilfe der Funktion getDeclaredConstructor(Class<?> ...)
abgefragt. Dann wird die Sichtbarkeit des Konstruktors umgeschaltet und durch den Aufruf newInstance(Object...)
ein neues Objekt vom Type ClassWithPrivateInterface
erzeugt.
//Call private constructor with parameter Constructor constructor = c.getDeclaredConstructor(String.class); constructor.setAccessible(true); ClassWithPrivateInterface privateInterfaceConstructor = (ClassWithPrivateInterface)constructor.newInstance("Constructor Parameter");
Fazit
Wie man an den Beispielen sieht, kommt man mit Java Reflection sehr weit. Es sollte auch klar sein, dass man solche "Konstrukte" in produktivem Code NIE einsetzen sollte, weil es meistens einen Sinn hat, dass eine Methode oder Variable private
ist. Allerdings kann es beim Erstellen von Unit-Tests nützlich sein, wenn man auf private Variablen und Methoden zugreifen kann. Das ist aber wirklich die Ausnahme.