Gibt es eine Möglichkeit, eine private Methode aufzurufen?

146

Ich habe eine Klasse, die XML und Reflection verwendet, um Objects an eine andere Klasse zurückzugeben.

Normalerweise sind diese Objekte Unterfelder eines externen Objekts, aber gelegentlich möchte ich sie im laufenden Betrieb generieren. Ich habe so etwas versucht, aber ohne Erfolg. Ich glaube, das liegt daran, dass Sie mit Java nicht auf privateMethoden zur Reflexion zugreifen können.

Element node = outerNode.item(0);
String methodName = node.getAttribute("method");
String objectName = node.getAttribute("object");

if ("SomeObject".equals(objectName))
    object = someObject;
else
    object = this;

method = object.getClass().getMethod(methodName, (Class[]) null);

Wenn die angegebene Methode ist private, schlägt sie mit a fehl NoSuchMethodException. Ich könnte es lösen, indem ich die Methode publicmache oder eine andere Klasse mache, von der ich sie ableiten kann.

Kurz gesagt, ich habe mich nur gefragt, ob es eine Möglichkeit gibt, privateüber Reflexion auf eine Methode zuzugreifen .

Sheldon Ross
quelle

Antworten:

303

Sie können eine private Methode mit Reflektion aufrufen. Ändern des letzten Bits des veröffentlichten Codes:

Method method = object.getClass().getDeclaredMethod(methodName);
method.setAccessible(true);
Object r = method.invoke(object);

Es gibt ein paar Einschränkungen. Erstens getDeclaredMethodwird nur die Methode gefunden, die in der aktuellen deklariert ist Classund nicht von Supertypen geerbt wurde. Durchlaufen Sie also gegebenenfalls die konkrete Klassenhierarchie. Zweitens SecurityManagerkann a die Verwendung der setAccessibleMethode verhindern. Daher muss es möglicherweise als PrivilegedAction(using AccessControlleroder Subject) ausgeführt werden.

erickson
quelle
2
Wenn ich dies in der Vergangenheit getan habe, habe ich nach dem Aufrufen der Methode auch method.setAccessible (false) aufgerufen, aber ich habe keine Ahnung, ob dies notwendig ist oder nicht.
Shsteimer
16
Nein, wenn Sie die Barrierefreiheit festlegen, gilt dies nur für diese Instanz. Solange Sie nicht zulassen, dass dieses bestimmte Methodenobjekt Ihrer Kontrolle entgeht, ist es sicher.
Erickson
6
Was bringt es also, private Methoden zu haben, wenn sie von außerhalb der Klasse aufgerufen werden können?
Peter Ajtai
2
Stellen Sie außerdem sicher, dass Sie getDeclaredMethod()statt nur aufrufen getMethod()- dies funktioniert nicht für private Methoden.
Ercksen
1
@PeterAjtai Entschuldigen Sie die verspätete Antwort, aber denken Sie so: Die meisten Menschen schließen heutzutage ihre Türen ab, obwohl sie wissen, dass das Schloss trivial aufgebrochen oder ganz umgangen werden kann. Warum? Weil es hilft, meist ehrliche Menschen ehrlich zu halten. Sie können sich vorstellen, dass der privateZugriff eine ähnliche Rolle spielt.
erickson
34

Verwenden Sie getDeclaredMethod()diese Option , um ein privates Methodenobjekt abzurufen und anschließend method.setAccessible()zuzulassen, dass es tatsächlich aufgerufen wird.

Mihai Toader
quelle
In meinem eigenen Beispiel ( stackoverflow.com/a/15612040/257233 ) erhalte ich eine, java.lang.StackOverflowErrorwenn ich nicht anrufesetAccessible(true) .
Robert Mark Bram
25

Wenn die Methode einen nicht primitiven Datentyp akzeptiert, kann die folgende Methode verwendet werden, um eine private Methode einer beliebigen Klasse aufzurufen:

public static Object genericInvokeMethod(Object obj, String methodName,
            Object... params) {
        int paramCount = params.length;
        Method method;
        Object requiredObj = null;
        Class<?>[] classArray = new Class<?>[paramCount];
        for (int i = 0; i < paramCount; i++) {
            classArray[i] = params[i].getClass();
        }
        try {
            method = obj.getClass().getDeclaredMethod(methodName, classArray);
            method.setAccessible(true);
            requiredObj = method.invoke(obj, params);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        return requiredObj;
    }

Die akzeptierten Parameter sind obj, methodName und die Parameter. Beispielsweise

public class Test {
private String concatString(String a, String b) {
    return (a+b);
}
}

Die Methode concatString kann als aufgerufen werden

Test t = new Test();
    String str = (String) genericInvokeMethod(t, "concatString", "Hello", "Mr.x");
gKaur
quelle
7
Warum wird paramCount benötigt? Können Sie nicht einfach params.length verwenden ?
Saad Malik
7

Sie können dies mit ReflectionTestUtils of Spring ( org.springframework.test.util.ReflectionTestUtils ) tun.

ReflectionTestUtils.invokeMethod(instantiatedObject,"methodName",argument);

Beispiel: Wenn Sie eine Klasse mit einer privaten Methode haben square(int x)

Calculator calculator = new Calculator();
ReflectionTestUtils.invokeMethod(calculator,"square",10);
KaderLAB
quelle
Es gibt auch eine begrenzte Anzahl von ReflectionUtils in der Kernfeder.
Thunderforge
3

Lassen Sie mich vollständigen Code für ausführungsgeschützte Methoden per Reflektion bereitstellen. Es unterstützt alle Arten von Parametern, einschließlich Generika, Autobox-Parametern und Nullwerten

@SuppressWarnings("unchecked")
public static <T> T executeSuperMethod(Object instance, String methodName, Object... params) throws Exception {
    return executeMethod(instance.getClass().getSuperclass(), instance, methodName, params);
}

public static <T> T executeMethod(Object instance, String methodName, Object... params) throws Exception {
    return executeMethod(instance.getClass(), instance, methodName, params);
}

@SuppressWarnings("unchecked")
public static <T> T executeMethod(Class clazz, Object instance, String methodName, Object... params) throws Exception {

    Method[] allMethods = clazz.getDeclaredMethods();

    if (allMethods != null && allMethods.length > 0) {

        Class[] paramClasses = Arrays.stream(params).map(p -> p != null ? p.getClass() : null).toArray(Class[]::new);

        for (Method method : allMethods) {
            String currentMethodName = method.getName();
            if (!currentMethodName.equals(methodName)) {
                continue;
            }
            Type[] pTypes = method.getParameterTypes();
            if (pTypes.length == paramClasses.length) {
                boolean goodMethod = true;
                int i = 0;
                for (Type pType : pTypes) {
                    if (!ClassUtils.isAssignable(paramClasses[i++], (Class<?>) pType)) {
                        goodMethod = false;
                        break;
                    }
                }
                if (goodMethod) {
                    method.setAccessible(true);
                    return (T) method.invoke(instance, params);
                }
            }
        }

        throw new MethodNotFoundException("There are no methods found with name " + methodName + " and params " +
            Arrays.toString(paramClasses));
    }

    throw new MethodNotFoundException("There are no methods found with name " + methodName);
}

Die Methode verwendet Apache ClassUtils, um die Kompatibilität von Autobox-Parametern zu überprüfen

Pavel Chertalev
quelle
Es macht absolut keinen Sinn, eine 9 Jahre alte Frage mit mehr als 90000 Ansichten und einer akzeptierten Antwort zu beantworten. Beantworten Sie stattdessen unbeantwortete Fragen.
Anuraag Baishya
0

Eine weitere Variante ist die Verwendung einer sehr leistungsstarken JOOR-Bibliothek https://github.com/jOOQ/jOOR

MyObject myObject = new MyObject()
on(myObject).get("privateField");  

Es ermöglicht das Ändern von Feldern wie endgültigen statischen Konstanten und das Aufrufen Ihrer geschützten Methoden, ohne dass in der Vererbungshierarchie eine konkrete Klasse angegeben wird

<!-- https://mvnrepository.com/artifact/org.jooq/joor-java-8 -->
<dependency>
     <groupId>org.jooq</groupId>
     <artifactId>joor-java-8</artifactId>
     <version>0.9.7</version>
</dependency>
Pavel Chertalev
quelle
0

Sie können Manifolds @Jailbreak für die direkte, typsichere Java-Reflexion verwenden:

@Jailbreak Foo foo = new Foo();
foo.callMe();

public class Foo {
    private void callMe();
}

@JailbreakSchaltet die foolokale Variable im Compiler für den direkten Zugriff auf alle Mitglieder in Fooder Hierarchie frei.

Ebenso können Sie die Erweiterungsmethode jailbreak () für die einmalige Verwendung verwenden:

foo.jailbreak().callMe();

Über die jailbreak()Methode können Sie auf jedes Mitglied in Fooder Hierarchie zugreifen .

In beiden Fällen löst der Compiler den Methodenaufruf für Sie typsicher wie eine öffentliche Methode auf, während Manifold unter der Haube einen effizienten Reflektionscode für Sie generiert.

Wenn der Typ statisch nicht bekannt ist, können Sie alternativ mithilfe der strukturellen Typisierung eine Schnittstelle definieren, die ein Typ erfüllen kann, ohne seine Implementierung deklarieren zu müssen. Diese Strategie gewährleistet die Typensicherheit und vermeidet Leistungs- und Identitätsprobleme im Zusammenhang mit Reflection und Proxy-Code.

Erfahren Sie mehr über Manifold .

Scott
quelle