Zugriff auf private Methoden, private Membervariablen und private innere Klassen mit Reflection

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.