Un modo per invocare un metodo privato?


146

Ho una classe che utilizza XML e reflection per restituire Objects ad un'altra classe.

Normalmente questi oggetti sono sottocampi di un oggetto esterno, ma a volte è qualcosa che voglio generare al volo. Ho provato qualcosa del genere ma senza risultati. Credo che sia perché Java non ti permetterà di accedere ai privatemetodi di riflessione.

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

Se il metodo fornito è private, non riesce con a NoSuchMethodException. Potrei risolverlo creando il metodo publico creando un'altra classe da cui derivarne.

Per farla breve, mi chiedevo solo se ci fosse un modo per accedere a un privatemetodo tramite la riflessione.

Risposte:


303

Puoi invocare il metodo privato con la riflessione. Modifica dell'ultimo bit del codice pubblicato:

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

Ci sono un paio di avvertenze. Innanzitutto, getDeclaredMethodtroverà solo il metodo dichiarato nell'attuale Class, non ereditato dai supertipi. Quindi, attraversa la gerarchia di classi concrete, se necessario. In secondo luogo, a SecurityManagerpuò impedire l'uso del setAccessiblemetodo. Pertanto, potrebbe essere necessario eseguirlo come PrivilegedAction(utilizzando AccessControllero Subject).


2
quando l'ho fatto in passato, ho anche chiamato method.setAccessible (false) dopo aver chiamato il metodo, ma non ho idea se sia necessario o meno.
shsteimer,

16
No, quando si imposta l'accessibilità, si applica solo a tale istanza. Finché non lasci che quel particolare oggetto Metodo sfugga al tuo controllo, è sicuro.
Erickson,

6
Allora, qual è il punto di avere metodi privati ​​se possono essere chiamati al di fuori della classe?
Peter Ajtai,

2
Assicurati anche di chiamare getDeclaredMethod()invece che semplicemente getMethod(): non funzionerà con metodi privati.
Ercksen,

1
@PeterAjtai Ci scusiamo per la risposta tardiva, ma pensala in questo modo: la maggior parte delle persone oggi chiude a chiave le porte, anche se sa che la serratura può essere banalmente rotta o elusa del tutto. Perché? Perché aiuta a mantenere oneste le persone per lo più oneste. Puoi pensare privateall'accesso che gioca un ruolo simile.
Erickson,


25

Se il metodo accetta un tipo di dati non primitivo, è possibile utilizzare il seguente metodo per richiamare un metodo privato di qualsiasi classe:

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

I parametri accettati sono obj, methodName e i parametri. Per esempio

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

Il metodo concatString può essere invocato come

Test t = new Test();
    String str = (String) genericInvokeMethod(t, "concatString", "Hello", "Mr.x");

7
Perché è necessario paramCount ? Non puoi semplicemente usare params.length ?
Saad Malik,


3

Consentitemi di fornire il codice completo per l'esecuzione di metodi protetti tramite reflection. Supporta qualsiasi tipo di parametro compresi generici, parametri autoboxed e valori null

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

Il metodo utilizza apache ClassUtils per verificare la compatibilità dei parametri autobox


Non ha assolutamente senso rispondere a una domanda di 9 anni con oltre 90000 visualizzazioni e una risposta accettata. Rispondi invece a domande senza risposta.
Anuraag Baishya,

0

Un'altra variante è l'utilizzo della libreria JOOR molto potente https://github.com/jOOQ/jOOR

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

Permette di modificare qualsiasi campo come le costanti statiche finali e di chiamare metodi protetti senza specificare la classe concreta nella gerarchia di ereditarietà

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

0

È possibile utilizzare @Jailbreak di Manifold per una riflessione Java diretta e sicura:

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

public class Foo {
    private void callMe();
}

@Jailbreaksblocca la foovariabile locale nel compilatore per l'accesso diretto a tutti i membri nella Foogerarchia di.

Allo stesso modo è possibile utilizzare il metodo di estensione jailbreak () per l'utilizzo una tantum:

foo.jailbreak().callMe();

Attraverso il jailbreak()metodo è possibile accedere a qualsiasi membro nella Foogerarchia.

In entrambi i casi, il compilatore risolve la chiamata del metodo per te digitando in modo sicuro, come se fosse un metodo pubblico, mentre Manifold genera un codice di riflessione efficiente per te.

In alternativa, se il tipo non è noto staticamente, è possibile utilizzare la digitazione strutturale per definire un'interfaccia che un tipo può soddisfare senza dover dichiararne l'implementazione. Questa strategia mantiene la sicurezza dei tipi ed evita i problemi di prestazioni e identità associati alla riflessione e al codice proxy.

Scopri di più sul collettore .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.