Pregunta Acceder a clases no visibles con reflexión


Estoy tratando de obtener una instancia de una clase no visible, una clase privada de paquete AKA, utilizando la reflexión. Me preguntaba si había una forma de cambiar los modificadores para hacerlo público y luego acceder a él usando Class.forName. Cuando intento eso ahora me impide decir que no puedo hacerlo. Desafortunadamente no hay setAccesible método de la Class clase.


32
2018-02-22 02:05


origen


Respuestas:


clase anidada - clase definida dentro de otra clase (incluye clases estáticas y no estáticas)
clase interna - clase anidada no estática (instancia de clase interna necesita instancia de clase externa para existir)

clases no anidadas (nivel superior)

Según su pregunta, sabemos que el constructor al que desea acceder no es público. Entonces tu clase puede verse así (A la clase está en un paquete diferente al nuestro)

package package1;

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

Para crear una instancia de esta clase, necesitamos llegar al constructor que queremos invocar y hacer que sea accesible. Cuando esté listo, podemos usar Constructor#newInstance(arguments) para crear instancia.

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();

clases anidadas e internas

Si desea acceder a clase anidada (estática y no estática) con Class.forName necesitas usar sintaxis:

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

Outer$Nested dice que Nested la clase es declarada dentro de Outer clase. Las clases anidadas son muy similares a los métodos, tienen acceso a todos los miembros de su clase externa (incluidas las privadas).

Pero debemos recordar que la instancia de clase interna que existe requiere una instancia de su clase externa. Normalmente los creamos a través de:

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

de modo que cuando vea que cada instancia de la clase interna tenga alguna información sobre su clase externa (la referencia a esa instancia externa se almacena en this$0 campo, más información: ¿Qué significa si una variable tiene el nombre "this $ 0" en IntelliJ IDEA al depurar Java?)

Entonces, al crear una instancia de Inner clase con Constructor#newInstance() necesita pasar como primera referencia de argumento a la instancia de Outer clase (para simular outer.new Inner() comportamiento).

Aquí hay un ejemplo.

en el paquete1

package package1;

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

en paquete2

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());

    }
}

clases anidadas estáticas

Las instancias de clases anidadas estáticas no requieren instancia de clase externa (ya que son estáticas). Entonces, en su caso, no necesitamos buscar un constructor con Outer.class como primer argumento. Y no necesitamos pasar instancia de clase externa como primer argumento. En otras palabras, el código será el mismo que para la clase no anidada (nivel superior) (tal vez, excepto el hecho de que necesitaría agregar $Nested sintaxis en Class.forName())


44
2018-02-22 02:28



Class.forName Deberia trabajar. Si la clase está dentro de una lista de jerarquía de paquetes en el "package.access" propiedad de seguridad, entonces deberá realizar la operación con el privilegio apropiado (generalmente todos los permisos, o no tiene un administrador de seguridad).

Si estás tratando de usar Class.newInstanceno lo hagas Class.newInstance maneja las excepciones mal. En lugar de obtener una Constructor y llama newInstance en ese. Es difícil ver con qué está teniendo problemas sin el rastro de excepción.

Como siempre, la mayoría pero no todos los usos de la reflexión son malas ideas.


2
2018-02-22 02:15



Recientemente lanzamos una biblioteca que ayuda mucho a acceder a campos privados, métodos y clases internas a través de la reflexión: BoundBox

Para una clase como

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

Proporciona una sintaxis como:

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

Lo único que tienes que hacer para crear la clase BoundBox es escribir @BoundBox(boundClass=Outer.class) y el BoundBoxOfOuter la clase se generará instantáneamente


0
2017-09-26 05:03



Tuve la necesidad de copiar el valor del campo de la versión anterior del objeto si el valor es nulo en la versión más reciente. Teníamos estas 2 opciones.

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 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
2017-11-26 07:53