Un moyen d'invoquer une méthode privée?

146

J'ai une classe qui utilise XML et la réflexion pour retourner Object s à une autre classe.

Normalement, ces objets sont des sous-champs d'un objet externe, mais parfois c'est quelque chose que je veux générer à la volée. J'ai essayé quelque chose comme ça mais en vain. Je pense que c'est parce que Java ne vous permettra pas d'accéder aux privateméthodes de réflexion.

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

Si la méthode fournie est private, elle échoue avec un NoSuchMethodException. Je pourrais le résoudre en créant la méthode public, ou en créant une autre classe pour la dériver.

Bref, je me demandais simplement s'il y avait un moyen d'accéder à une privateméthode par réflexion.

Sheldon Ross
la source

Réponses:

303

Vous pouvez invoquer une méthode privée avec réflexion. Modification du dernier bit du code affiché:

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

Il y a quelques mises en garde. Premièrement, getDeclaredMethodne trouvera que la méthode déclarée dans le courant Class, non héritée des supertypes. Alors, parcourez la hiérarchie des classes concrètes si nécessaire. Deuxièmement, a SecurityManagerpeut empêcher l'utilisation de la setAccessibleméthode. Ainsi, il peut être nécessaire de l'exécuter en tant que PrivilegedAction(en utilisant AccessControllerou Subject).

Erickson
la source
2
quand j'ai fait cela dans le passé, j'ai également appelé method.setAccessible (false) après avoir appelé la méthode, mais je n'ai aucune idée si cela est nécessaire ou non.
shsteimer
16
Non, lorsque vous définissez l'accessibilité, cela ne s'applique qu'à cette instance. Tant que vous ne laissez pas cet objet Method particulier échapper à votre contrôle, c'est sûr.
erickson
6
Alors, quel est l'intérêt d'avoir des méthodes privées si elles peuvent être appelées de l'extérieur de la classe?
Peter Ajtai
2
Assurez-vous également d'appeler getDeclaredMethod()au lieu de simplement getMethod()- cela ne fonctionnera pas pour les méthodes privées.
Ercksen
1
@PeterAjtai Désolé pour la réponse tardive, mais pensez-y de cette façon: la plupart des gens de nos jours verrouillent leurs portes, même s'ils savent que la serrure peut être brisée ou contournée. Pourquoi? Parce que cela aide à garder honnêtes les gens pour la plupart honnêtes. Vous pouvez penser à l' privateaccès jouant un rôle similaire.
erickson
34

Utilisez getDeclaredMethod()pour obtenir un objet Method privé, puis utilisez method.setAccessible()pour permettre de l'appeler réellement.

Mihai Toader
la source
Dans mon propre exemple ( stackoverflow.com/a/15612040/257233 ), j'obtiens un java.lang.StackOverflowErrorsi je n'appelle pas setAccessible(true).
Robert Mark Bram
25

Si la méthode accepte un type de données non primitif, la méthode suivante peut être utilisée pour appeler une méthode privée de n'importe quelle 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;
    }

Les paramètres acceptés sont obj, methodName et les paramètres. Par exemple

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

La méthode concatString peut être appelée comme

Test t = new Test();
    String str = (String) genericInvokeMethod(t, "concatString", "Hello", "Mr.x");
gKaur
la source
7
Pourquoi paramCount est- il nécessaire? Vous ne pouvez pas simplement utiliser params.length ?
Saad Malik
8

vous pouvez le faire en utilisant ReflectionTestUtils de Spring ( org.springframework.test.util.ReflectionTestUtils )

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

Exemple: si vous avez une classe avec une méthode privée square(int x)

Calculator calculator = new Calculator();
ReflectionTestUtils.invokeMethod(calculator,"square",10);
KaderLAB
la source
Il existe également un ReflectionUtils plus limité dans Core Spring.
Thunderforge
3

Permettez-moi de fournir un code complet pour les méthodes protégées d'exécution via la réflexion. Il prend en charge tous les types de paramètres, y compris les génériques, les paramètres autoboxed et les valeurs nulles

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

La méthode utilise Apache ClassUtils pour vérifier la compatibilité des paramètres autoboxed

Pavel Chertalev
la source
Il est absolument inutile de répondre à une question de 9 ans avec plus de 90000 vues et une réponse acceptée. Répondez plutôt aux questions sans réponse.
Anuraag Baishya
0

Une autre variante utilise la très puissante bibliothèque JOOR https://github.com/jOOQ/jOOR

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

Il permet de modifier tous les champs comme les constantes statiques finales et d'appeler des méthodes protégées sans spécifier de classe concrète dans la hiérarchie d'héritage

<!-- 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
la source
0

Vous pouvez utiliser @Jailbreak de Manifold pour une réflexion Java directe et sécurisée:

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

public class Foo {
    private void callMe();
}

@Jailbreakdéverrouille la foovariable locale dans le compilateur pour un accès direct à tous les membres de Foola hiérarchie de.

De même, vous pouvez utiliser la méthode d'extension jailbreak () pour une utilisation ponctuelle:

foo.jailbreak().callMe();

Grâce à la jailbreak()méthode, vous pouvez accéder à n'importe quel membre de Foola hiérarchie de.

Dans les deux cas, le compilateur résout l'appel de méthode pour vous en toute sécurité, comme s'il s'agissait d'une méthode publique, tandis que Manifold génère un code de réflexion efficace pour vous sous le capot.

Sinon, si le type n'est pas connu de manière statique, vous pouvez utiliser le typage structurel pour définir une interface qu'un type peut satisfaire sans avoir à déclarer son implémentation. Cette stratégie maintient la sécurité de type et évite les problèmes de performances et d'identité associés à la réflexion et au code proxy.

En savoir plus sur Manifold .

Scott
la source