Alguma maneira de invocar um método privado?

146

Eu tenho uma classe que usa XML e reflexão para retornar Objects para outra classe.

Normalmente, esses objetos são subcampos de um objeto externo, mas, ocasionalmente, é algo que eu quero gerar rapidamente. Eu tentei algo assim, mas sem sucesso. Acredito que é porque o Java não permitirá que você acesse privatemétodos de reflexão.

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 o método fornecido for private, ele falhará com a NoSuchMethodException. Eu poderia resolvê-lo criando o método publicou criando outra classe para derivá-lo.

Para encurtar a história, eu queria saber se havia uma maneira de acessar um privatemétodo via reflexão.

Sheldon Ross
fonte

Respostas:

303

Você pode chamar o método privado com reflexão. Modificando o último bit do código publicado:

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

Existem algumas ressalvas. Primeiro, getDeclaredMethodencontrará apenas o método declarado no atual Class, não herdado dos supertipos. Portanto, percorra a hierarquia de classes concreta, se necessário. Segundo, um SecurityManagerpode impedir o uso do setAccessiblemétodo. Portanto, pode ser necessário executar como um PrivilegedAction(usando AccessControllerou Subject).

erickson
fonte
2
quando fiz isso no passado, também chamei method.setAccessible (false) depois de chamar o método, mas não faço ideia se isso é necessário ou não.
Shsteimer 19/05/09
16
Não, quando você define a acessibilidade, ela se aplica apenas a essa instância. Contanto que você não permita que esse objeto Method específico escape do seu controle, é seguro.
217 erickson
6
Então, qual é o sentido de ter métodos privados se eles puderem ser chamados de fora da classe?
Peter Ajtai
2
Certifique-se de ligar em getDeclaredMethod()vez de apenas getMethod()- isso não funcionará para métodos particulares.
Ercksen
1
@PeterAjtai Desculpe pela resposta tardia, mas pense dessa maneira: a maioria das pessoas hoje em dia trava as portas, mesmo sabendo que a trava pode ser trivialmente quebrada ou contornada. Por quê? Porque ajuda a manter honestamente as pessoas honestas. Você pode pensar em privateacessar desempenhando um papel semelhante.
21718
34

Use getDeclaredMethod()para obter um objeto Method privado e, em seguida, use method.setAccessible()para permitir chamá-lo realmente.

Mihai Toader
fonte
No meu próprio exemplo ( stackoverflow.com/a/15612040/257233 ), recebo um java.lang.StackOverflowErrorse eu não ligar setAccessible(true).
precisa
25

Se o método aceitar um tipo de dados não primitivo, o método a seguir poderá ser usado para chamar um método privado de qualquer 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;
    }

O Parâmetro aceito é obj, methodName e os parâmetros. Por exemplo

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

O método concatString pode ser chamado como

Test t = new Test();
    String str = (String) genericInvokeMethod(t, "concatString", "Hello", "Mr.x");
gKaur
fonte
7
Por que o paramCount é necessário? Você não pode simplesmente usar params.length ?
Saad Malik
7

você pode fazer isso usando ReflectionTestUtils of Spring ( org.springframework.test.util.ReflectionTestUtils )

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

Exemplo: se você tem uma classe com um método privado square(int x)

Calculator calculator = new Calculator();
ReflectionTestUtils.invokeMethod(calculator,"square",10);
KaderLAB
fonte
Há também um ReflectionUtils mais limitado no núcleo Spring.
Thunderforge
3

Deixe-me fornecer um código completo para execução de métodos protegidos via reflexão. Ele suporta todos os tipos de parâmetros, incluindo genéricos, parâmetros de caixa automática e 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);
}

O método usa o apache ClassUtils para verificar a compatibilidade de parâmetros automáticos.

Pavel Chertalev
fonte
Não faz sentido responder a uma pergunta de 9 anos com mais de 90000 visualizações e uma resposta aceita. Responda a perguntas não respondidas.
Anuraag Baishya
0

Mais uma variante está usando a biblioteca JOOR muito poderosa https://github.com/jOOQ/jOOR

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

Permite modificar todos os campos, como constantes estáticas finais, e chamar métodos protegidos yne sem especificar classe concreta na hierarquia de herança

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

Você pode usar o @Jailbreak do Manifold para refletir Java diretamente com segurança de tipo:

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

public class Foo {
    private void callMe();
}

@Jailbreakdesbloqueia a foovariável local no compilador para acesso direto a todos os membros na Foohierarquia.

Da mesma forma, você pode usar o método de extensão jailbreak () para uso único:

foo.jailbreak().callMe();

Através do jailbreak()método, você pode acessar qualquer membro da Foohierarquia.

Nos dois casos, o compilador resolve a chamada do método para você digitar com segurança, como se fosse um método público, enquanto o Manifold gera um código de reflexão eficiente para você.

Como alternativa, se o tipo não for conhecido estaticamente, você poderá usar a Tipagem Estrutural para definir uma interface que um tipo possa satisfazer sem precisar declarar sua implementação. Essa estratégia mantém a segurança de tipo e evita problemas de desempenho e identidade associados ao código de reflexão e proxy.

Descubra mais sobre o coletor .

Scott
fonte