ti-enxame.com

Acessando classes não visíveis com reflexão

Eu estou tentando obter uma instância de uma classe não visível, classe privada do pacote AKA, usando reflexão. Eu queria saber se havia uma maneira de mudar os modificadores para torná-lo público e, em seguida, acessá-lo usando Class.forName. Quando eu tento isso agora, me impede de dizer que não posso fazer isso. Infelizmente não existe o método setAccesible da classe Class.

37
Josh Sobel

aninhada classe - classe definida dentro de outra classe (inclui classes estáticas e não-estáticas)
inner class - classe aninhada não estática (instância da classe interna precisa da instância da classe externa existir)

classes não aninhadas (nível superior)

Com base na sua pergunta, sabemos que o construtor que você deseja acessar não é público. Então, sua classe pode se parecer com isso (a classe A está em algum pacote diferente do nosso)

package package1;

public class A {
    A(){
        System.out.println("this is non-public constructor");
    }
}

Para criar uma instância dessa classe, precisamos chegar ao construtor que queremos invocar e torná-lo acessível. Quando isso é feito, podemos usar Constructor#newInstance(arguments) para criar uma instância.

Class<?> c = Class.forName("package1.A");
//full package name --------^^^^^^^^^^
//or simpler without Class.forName:
//Class<package1.A> c = package1.A.class;

//In our case we need to use
Constructor<?> constructor = c.getDeclaredConstructor();
//note: getConstructor() can return only public constructors
//so we needed to search for any Declared constructor

//now we need to make this constructor accessible
constructor.setAccessible(true);//ABRACADABRA!

Object o = constructor.newInstance();

classes aninhadas e internas

Se você quiser acessar a classe aninhada (estática e não-estática) com Class.forName, será necessário usar a sintaxe:

Class<?> clazz = Class.forName("package1.Outer$Nested");

Outer$Nested diz que a classe Nested é declarada dentro da classe Outer. As classes aninhadas são muito semelhantes aos métodos, elas têm acesso a todos os membros de sua classe externa (incluindo os privados). 

Mas precisamos lembrar que a instância da classe interna para a qual existe requer uma instância de sua classe externa. Normalmente nós os criamos via:

Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();

assim como você vê cada instância da classe Inner tem alguma informação sobre sua classe externa (referência a essa instância externa é armazenada no campo this$0, mais info: O que significa se uma variável tem o nome "this $ 0" no IntelliJ IDEA durante a depuração do Java? )

Portanto, ao criar a instância da classe Inner com Constructor#newInstance(), você precisa passar como referência do primeiro argumento para a instância da classe Outer (para simular o comportamento outer.new Inner()).

Aqui está um exemplo.

no package1

package package1;

public class Outer {
    class Inner{
        Inner(){
            System.out.println("non-public constructor of inner class");
        }
    }
}

no package2 

package package2;

import package1.Outer;
import Java.lang.reflect.Constructor;

public class Test {
    public static void main(String[] args) throws Exception {

        Outer outerObject = new Outer();

        Class<?> innerClazz = Class.forName("package1.Outer$Inner");

        // constructor of inner class as first argument need instance of
        // Outer class, so we need to select such constructor
        Constructor<?> constructor = innerClazz.getDeclaredConstructor(Outer.class);

        //we need to make constructor accessible 
        constructor.setAccessible(true);

        //and pass instance of Outer class as first argument
        Object o = constructor.newInstance(outerObject);

        System.out.println("we created object of class: "+o.getClass().getName());

    }
}

classes aninhadas estáticas

Instâncias de classes aninhadas estáticas não exigem instância da classe Outer (já que são estáticas). Portanto, no caso deles, não precisamos procurar o construtor com Outer.class como primeiro argumento. E não precisamos passar a instância da classe externa como primeiro argumento. Em outras palavras, o código será o mesmo que para a classe não aninhada (nível superior) (talvez exceto pelo fato de que você precisaria adicionar a sintaxe $Nested em Class.forName()).

49
Pshemo

Class.forName deve funcionar. Se a classe estiver em uma lista de hierarquia de pacotes na propriedade de segurança "package.access", será necessário executar a operação com o privilégio apropriado (geralmente todas as permissões; ou não ter um gerenciador de segurança).

Se você está tentando usar Class.newInstance, não tente. Class.newInstance lida com exceções mal. Em vez disso, obtenha um Constructor e chame newInstance disso. É difícil ver com o que você está tendo problemas sem o rastreamento de exceção.

Como sempre, a maioria, mas não todos, os usos da reflexão são idéias ruins.

3
Tom Hawtin - tackline

Eu tinha o requisito de copiar o valor do campo da versão mais antiga do objeto se o valor fosse null na versão mais recente. Nós tivemos essas duas opções. 

Core Java: 

for (Field f : object.getClass().getSuperclass().getDeclaredFields()) {
    f.setAccessible(true);
  System.out.println(f.getName());
  if (f.get(object) == null){
    f.set(object, f.get(oldObject));
  }
}

Usando o Spring [org.springframework.beans.BeanWrapper]:

BeanWrapper bw = new BeanWrapperImpl(object);
PropertyDescriptor[] data = bw.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : data) {
  System.out.println(propertyDescriptor.getName());
  Object propertyValue = bw.getPropertyValue(propertyDescriptor.getName());
  if(propertyValue == null )
    bw.setPropertyValue( new PropertyValue(propertyDescriptor.getName(),"newValue"));
}
0
Quest_for_java

Você pode usar Manifold@Jailbreak para reflexão Java direta e com segurança de tipos:

Foo foo = new @Jailbreak Foo();

public class Foo {
    Foo() {...}

    private void yodog() {...}
}

Aqui @Jailbreak habilita o compilador a resolver o construtor com segurança de tipo como se fosse público, enquanto o Manifold gera um código de reflexão eficiente para você sob o capô.

Além disso, você pode usar @Jailbreak para acessar e construir classes não visíveis:

com.abc. @Jailbreak Bar bar = new com.abc. @Jailbreak Bar();

package com.abc;
// package-private class
class Bar {
    Bar() {...}
}

Para acesso à classe oculta, a gramática de anotação do Java requer que a classe seja anotada separadamente de seu pacote.

Mais geralmente você pode usar @Jailbreak para qualquer tipo de reflexão:

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

@Jailbreak desbloqueia a variável local foo no compilador para acesso direto a todos os membros na hierarquia de Foo.

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

foo.jailbreak().yodog();

Através do método jailbreak() você pode acessar qualquer membro na hierarquia de Foo.

Descubra mais sobre Manifold .

0
Scott McKinney

Nós lançamos recentemente uma biblioteca que ajuda muito a acessar campos privados, métodos e classes internas através da reflexão: BoundBox

Para uma aula como 

public class Outer {
    private static class Inner {
        private int foo() {return 2;}
    }
}

Ele fornece uma sintaxe como: 

Outer outer = new Outer();
Object inner = BoundBoxOfOuter.boundBox_new_Inner();
new BoundBoxOfOuter.BoundBoxOfInner(inner).foo();

A única coisa que você precisa fazer para criar a classe BoundBox é escrever @BoundBox(boundClass=Outer.class) e a classe BoundBoxOfOuter será gerada instantaneamente.

0
Snicolas