¿Alguna forma de invocar un método privado?


146

Tengo una clase que usa XML y reflexión para devolver Objects a otra clase.

Normalmente estos objetos son subcampos de un objeto externo, pero ocasionalmente es algo que quiero generar sobre la marcha. He intentado algo como esto pero fue en vano. Creo que es porque Java no le permitirá acceder a privatemétodos de reflexión.

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 el método proporcionado es private, falla con a NoSuchMethodException. Podría resolverlo haciendo el método public, o haciendo otra clase para derivarlo.

En pocas palabras, me preguntaba si había una manera de acceder a un privatemétodo a través de la reflexión.

Respuestas:


303

Puede invocar un método privado con reflexión. Modificando el último bit del código publicado:

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

Hay un par de advertencias. Primero, getDeclaredMethodsolo encontrará el método declarado en el actual Class, no heredado de los supertipos. Entonces, recorre la jerarquía de clases concreta si es necesario. En segundo lugar, a SecurityManagerpuede evitar el uso del setAccessiblemétodo. Por lo tanto, es posible que deba ejecutarse como a PrivilegedAction(usando AccessControllero Subject).


2
cuando hice esto en el pasado, también llamé a method.setAccessible (false) después de llamar al método, pero no tengo idea de si esto es necesario o no.
shsteimer

16
No, cuando configura la accesibilidad, solo se aplica a esa instancia. Siempre y cuando no permita que ese objeto Método en particular escape de su control, es seguro.
erickson

66
Entonces, ¿cuál es el punto de tener métodos privados si se pueden llamar desde fuera de la clase?
Peter Ajtai

2
También asegúrese de llamar en getDeclaredMethod()lugar de solo getMethod(): esto no funcionará para métodos privados.
Ercksen

1
@PeterAjtai Perdón por la respuesta tardía, pero piénselo de esta manera: la mayoría de las personas hoy en día cierran sus puertas, a pesar de que saben que la cerradura puede romperse o eludirse por completo. ¿Por qué? Porque ayuda a mantener honestas a las personas mayormente honestas Puedes pensar en el privateacceso jugando un papel similar.
erickson


25

Si el método acepta un tipo de datos no primitivo, se puede utilizar el siguiente método para invocar un método privado de cualquier clase:

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

Los parámetros aceptados son obj, methodName y los parámetros. Por ejemplo

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

El método concatString se puede invocar como

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

77
¿Por qué se necesita paramCount ? ¿No puedes usar params.length ?
Saad Malik


3

Permítanme proporcionar un código completo para la ejecución de métodos protegidos a través de la reflexión. Admite cualquier tipo de parámetros, incluidos genéricos, parámetros autoboxed y valores nulos

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

El método usa Apache ClassUtils para verificar la compatibilidad de los parámetros autoboxed


No tiene absolutamente ningún sentido responder una pregunta de 9 años con más de 90000 visitas y una respuesta aceptada. Responda las preguntas sin respuesta en su lugar.
Anuraag Baishya

0

Una variante más es usar la biblioteca JOOR muy poderosa https://github.com/jOOQ/jOOR

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

Permite modificar cualquier campo como las constantes estáticas finales y llamar a métodos protegidos y sin especificar una clase concreta en la jerarquía de herencia

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

Puede utilizar Manifold's @Jailbreak para una reflexión Java directa y segura de tipos:

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

public class Foo {
    private void callMe();
}

@Jailbreakdesbloquea la foovariable local en el compilador para acceder directamente a todos los miembros de Foola jerarquía.

Del mismo modo, puede usar el método de extensión jailbreak () para un uso único:

foo.jailbreak().callMe();

A través del jailbreak()método, puede acceder a cualquier miembro de Foola jerarquía.

En ambos casos, el compilador resuelve la llamada al método para que escriba con seguridad, como si fuera un método público, mientras que Manifold genera un código de reflexión eficiente para usted bajo el capó.

Alternativamente, si el tipo no se conoce estáticamente, puede usar la tipificación estructural para definir una interfaz que un tipo puede satisfacer sin tener que declarar su implementación. Esta estrategia mantiene la seguridad de tipos y evita problemas de rendimiento e identidad asociados con la reflexión y el código proxy.

Descubre más sobre Colector .

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.