Acceso a clases no visibles con reflexión


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

Author: Tom Hawtin - tackline, 2013-02-22

4 answers

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)

Basándonos en su pregunta, sabemos que el constructor al que desea acceder no es público. Así que su clase puede verse así (A la clase está en algún 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 hacerla accesible. Cuando esté hecho podemos usar Constructor#newInstance(arguments) para crear una 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 la clase anidada (estática y no estática) con Class.forName necesita usar la sintaxis:

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

Outer$Nested dice que la clase Nested se declara dentro de la clase Outer. Las clases anidadas son muy similares a los métodos, tienen acceso a todos los miembros de su clase externa (incluidos los privados).

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

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

Así que como ves cada instancia de la clase Interna tiene alguna información sobre su clase externa (la referencia a esa instancia externa se almacena en el campo this$0, más información: ¿Qué significa si una variable tiene el nombre "this 0 0" en IntelliJ IDEA mientras se depura Java?)

Así que al crear una instancia de Inner clase con Constructor#newInstance() debe pasar como primer argumento la referencia a la instancia de la clase Outer (para simular el comportamiento outer.new Inner()).

Aquí hay un ejemplo.

En el envase 1

package package1;

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

En el envase 2

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). Así que en su caso no necesitamos buscar constructor con Outer.class como primer argumento. Y no necesitamos pasar instancia de clase externa como primera argumento. En otras palabras, el código será el mismo que para la clase no anidada (de nivel superior) (tal vez excepto el hecho de que tendría que agregar $Nested sintaxis en Class.forName()).

 45
Author: Pshemo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:26

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

Si está tratando de usar Class.newInstance, no lo haga. Class.newInstance maneja las excepciones mal. En su lugar obtener un Constructor y llamar newInstance en eso. 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
Author: Tom Hawtin - tackline,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-22 02:20:04

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 la clase BoundBoxOfOuter se generará instantáneamente.

 0
Author: Snicolas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-26 05:03:33

Tuve el requisito de copiar el valor del campo de la versión anterior del objeto si el valor es nulo en la última versión. Teníamos estas 2 opciones.

Java central:

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.frijol.Envasadora de frijoles]:

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
Author: Quest_for_java,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-11 15:25:46