Question

J'ai une classe qui utilise le langage XML et de réflexion pour revenir Objects à une autre classe.

Normalement, ces objets sont des champs sous d'un objet extérieur, mais parfois il est quelque chose que je veux générer à la volée. J'ai essayé quelque chose comme ça, mais en vain. Je crois que ce parce que Java ne vous permettra pas d'accéder à des méthodes de 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, il échoue avec un NoSuchMethodException. Je pourrais le résoudre en faisant la méthode public, ou faire une autre classe pour dériver de.

Longue histoire courte, je me demandais s'il y avait un moyen d'accéder à une méthode de private par réflexion.

Était-ce utile?

La solution

Vous pouvez appeler la méthode privée avec la 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. Tout d'abord, getDeclaredMethod ne méthode trouver déclarée dans le Class actuel, pas hérité de supertypes. Alors, parcourir la hiérarchie de classe concrète si nécessaire. En second lieu, un SecurityManager peut empêcher l'utilisation de la méthode de setAccessible. Ainsi, il peut avoir besoin de fonctionner comme un PrivilegedAction (en utilisant AccessController ou Subject).

Autres conseils

Utilisez getDeclaredMethod() pour obtenir un objet de méthode privée puis utiliser method.setAccessible() pour permettre d'appeler réellement.

Si la méthode accepte le type de données non-primitive alors la méthode suivante peut être utilisée pour invoquer une méthode privée de toute catégorie:

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

Le paramètre accepté sont obj, methodName et les paramètres. Par exemple

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

Méthode concatString peut être invoquée comme

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

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

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

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

Calculator calculator = new Calculator();
ReflectionTestUtils.invokeMethod(calculator,"square",10);

Permettez-moi de code complet pour l'exécution des méthodes protégées par la réflexion. Il prend en charge tous les types de params y compris les génériques, params autoboxed et des 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 ClassUtils Apache pour vérifier la compatibilité de params autoboxed

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

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

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

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

Vous pouvez utiliser Manifold @Jailbreak directe, le type réflexion -safe Java:

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

public class Foo {
    private void callMe();
}

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

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

foo.jailbreak().callMe();

Grâce à la méthode jailbreak() vous pouvez accéder à tous les membres dans la hiérarchie de Foo.

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

Par ailleurs, si le type est connu statiquement, vous pouvez utiliser Typing structurel pour définir une interface de type peut satisfaire sans avoir à déclarer sa mise en œuvre. Cette stratégie maintient type sécurité et évite les problèmes de performance et d'identité associés à un code de réflexion et de proxy.

Découvrez plus sur Manifold.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top