web-dev-qa-db-fra.com

Une manière d'invoquer une méthode privée?

J'ai une classe qui utilise XML et la réflexion pour renvoyer Objects à une autre classe.

Normalement, ces objets sont des sous-champs d’un objet externe, mais c’est parfois 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 méthodes private pour la 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 rendant la méthode public ou en faisant en sorte qu’une autre classe le dérive.

Bref, je me demandais s’il existait un moyen d’accéder à une méthode private par réflexion.

124
Sheldon Ross

Vous pouvez invoquer une méthode privée avec réflexion. Modifier le dernier bit du code posté:

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

Il y a quelques mises en garde. Premièrement, getDeclaredMethod ne trouvera que la méthode déclarée dans la Class courante, non héritée des supertypes. Alors, parcourez la hiérarchie concrète des classes si nécessaire. Deuxièmement, une SecurityManager peut empêcher l'utilisation de la méthode setAccessible. Ainsi, il peut être nécessaire d’exécuter en tant que PrivilegedAction (en utilisant AccessController ou Subject).

259
erickson

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

33
Mihai Toader

Si la méthode accepte un type de données non primitif, vous pouvez utiliser la méthode suivante 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 en tant que 

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

Permettez-moi de fournir un code complet pour les méthodes d'exécution protégées via la réflexion. Il prend en charge tous les types de paramètres, y compris les génériques, les paramètres à sélection automatique et les valeurs 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);
}

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

3
Pavel Chertalev

vous pouvez le faire en utilisant ReflectionTestUtils of 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);
2
KaderLAB

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

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

Il permet de modifier des champs tels que 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>
0
Pavel Chertalev

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

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

public class Foo {
    private void callMe();
}

@Jailbreak déverrouille la variable locale foo dans le compilateur pour un accès direct à tous les membres de la hiérarchie de Foo.

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

foo.jailbreak().callMe();

Avec la méthode jailbreak(), vous pouvez accéder à n’importe quel membre de la hiérarchie de Foo.

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 statiquement, vous pouvez utiliser Typage structurel pour définir une interface qu'un type peut satisfaire sans avoir à déclarer sa mise en oeuvre. Cette stratégie préserve la sécurité des types et évite les problèmes de performances et d’identité associés aux codes de réflexion et de proxy.

En savoir plus sur Collecteur .

0
Scott McKinney