¿Cómo leer el valor de un campo privado de una clase diferente en Java?

482

Tengo una clase mal diseñada en un tercero JARy necesito acceder a uno de sus campos privados . Por ejemplo, ¿por qué debería elegir un campo privado si es necesario?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

¿Cómo puedo usar la reflexión para obtener el valor de stuffIWant?

Frank Krueger
fuente

Respuestas:

668

Para acceder a los campos privados, debe obtenerlos de los campos declarados de la clase y luego hacerlos accesibles:

Field f = obj.getClass().getDeclaredField("stuffIWant"); //NoSuchFieldException
f.setAccessible(true);
Hashtable iWantThis = (Hashtable) f.get(obj); //IllegalAccessException

EDITAR : como ha comentado aperkins , tanto acceder al campo, configurarlo como accesible y recuperar el valor puede arrojar Exceptions, aunque las únicas excepciones marcadas de las que debe tener en cuenta se comentaron anteriormente.

Se NoSuchFieldExceptiongeneraría si solicitara un campo con un nombre que no correspondiera a un campo declarado.

obj.getClass().getDeclaredField("misspelled"); //will throw NoSuchFieldException

Se IllegalAccessExceptiongeneraría si el campo no fuera accesible (por ejemplo, si es privado y no se ha hecho accesible al perder la f.setAccessible(true)línea).

Los RuntimeExceptions que se pueden lanzar son SecurityExceptions (si las JVM SecurityManagerno le permitirán cambiar la accesibilidad de un campo), o IllegalArgumentExceptions, si intenta acceder al campo en un objeto que no es del tipo de clase del campo:

f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type
oxbow_lakes
fuente
44
¿Pueden explicar los comentarios de excepción? se ejecutará el código pero arrojará excepciones? o el código puede arrojar excepciones?
Nir Levy
1
@Nir - No, con toda probabilidad el código se ejecutará bien (ya que el SecurityManager predeterminado permitirá cambiar la accesibilidad de los campos), pero debe manejar las excepciones verificadas (ya sea capturarlas o declararlas para que se vuelvan a lanzar ). He modificado un poco mi respuesta. Podría ser bueno para que usted escriba un pequeño caso de prueba para jugar alrededor y ver lo que sucede
oxbow_lakes
3
Lo siento, esta respuesta es confusa para mí. Quizás muestre un ejemplo de manejo típico de excepciones. Parece que las excepciones ocurrirían cuando las clases se conectan incorrectamente. El ejemplo de código hace que parezca que las excepciones se lanzarían a las líneas correspondientes.
LaFayette
2
getDeclaredField()no encuentra campos si están definidos en clases principales: también debe iterar hacia arriba a través de la jerarquía de clases principales, llamando getDeclaredField()a cada uno, hasta que encuentre una coincidencia (después de lo cual puede llamar setAccessible(true)), o llegue Object.
Luke Hutchison
1
@legend puede instalar un administrador de seguridad para prohibir dicho acceso. Desde Java 9, no se supone que dicho acceso funcione a través de las fronteras del módulo (a menos que un módulo esté abierto explícitamente), aunque hay una fase de transición con respecto a la aplicación de las nuevas reglas. Además de eso, requerir un esfuerzo adicional como Reflection siempre hizo una diferencia para los no privatecampos. Impide el acceso accidental.
Holger
159

Pruebe FieldUtilsdesde apache commons-lang3:

FieldUtils.readField(object, fieldName, true);
yegor256
fuente
76
Estoy convencido de que podría resolver la mayoría de los problemas del mundo al unir algunos métodos de commons-lang3.
Cameron
@yegor ¿por qué Java permite esto? ¿Por qué Java permite acceder a miembros privados?
Asif Mushtaq
1
@ yegor256 ¡¡Todavía puedo acceder a miembros privados de C # y C ++ también ... !! ¿Entonces? todos los creadores de idiomas son débiles?
Asif Mushtaq
3
@UnKnown java no. Hay dos cosas diferentes: lenguaje Java y Java como máquina virtual Java. Este último opera en bytecode. Y hay bibliotecas para manipular eso. Por lo tanto, el lenguaje Java no le permite usar campos privados fuera del alcance o no permite mutar las referencias finales. Pero es posible hacerlo usando la herramienta. Lo que sucede que está dentro de la biblioteca estándar.
evgenii
3
@Desconocido, uno de los motivos es que Java no tiene acceso "amigo" para permitir el acceso al campo solo por un marco de infraestructura como DI, ORM o serializador XML / JSON. Tales marcos necesitan acceso a los campos de objeto para serializar o inicializar correctamente el estado interno de un objeto, pero aún puede necesitar la aplicación adecuada de encapsulación en tiempo de compilación para su lógica empresarial.
Ivan Gammel
26

La reflexión no es la única forma de resolver su problema (que es acceder a la funcionalidad / comportamiento privado de una clase / componente)

Una solución alternativa es extraer la clase del .jar, descompilarla usando (por ejemplo) Jode o Jad , cambiar el campo (o agregar un descriptor de acceso) y recompilarlo contra el .jar original. Luego coloque el nuevo .class delante del .jaren el classpath, o vuelva a insertarlo en el .jar. (la utilidad jar le permite extraer y reinsertar en un .jar existente)

Como se señala a continuación, esto resuelve el problema más amplio de acceder / cambiar el estado privado en lugar de simplemente acceder / cambiar un campo.

Esto requiere que .jarno se firme, por supuesto.

Brian Agnew
fuente
36
De esta manera será bastante doloroso para un campo simple.
Valentin Rocher el
1
Estoy en desacuerdo. Le permite no solo acceder a su campo, sino también cambiar la clase si es necesario si el acceso al campo no es suficiente.
Brian Agnew
44
Entonces tienes que hacer eso de nuevo. ¿Qué sucede si el jar recibe una actualización y usa la reflexión para un campo que ya no existe? Es exactamente el mismo problema. Solo tienes que manejarlo.
Brian Agnew
44
Me sorprende lo mucho que esto se rechaza dado a) se destaca como una alternativa práctica b) atiende escenarios en los que cambiar la visibilidad de un campo no es suficiente
Brian Agnew
2
@BrianAgnew tal vez sea simplemente semántico, pero si nos atenemos a la pregunta (usar la reflexión para leer un campo privado) no usar la reflexión es de inmediato una auto contradicción. Pero estoy de acuerdo en que proporciona acceso al campo ... pero aún así el campo ya no es privado, así que nuevamente no nos atenemos al "leer un campo privado" de la pregunta. Desde otro ángulo, la modificación .jar puede no funcionar en algunos casos (jar firmado), debe hacerse cada vez que se actualiza el jar, requiere una manipulación cuidadosa del classpath (que puede no controlar completamente si se ejecuta en un contenedor de aplicación) etc.
Remi Morin
18

Otra opción que aún no se ha mencionado: usar Groovy . Groovy le permite acceder a variables de instancia privadas como efecto secundario del diseño del lenguaje. Ya sea que tenga o no un captador para el campo, simplemente puede usar

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()
lucas
fuente
44
El OP solicitó específicamente Java
Jochen
3
Muchos proyectos de Java hoy incorporan groovy. Es suficiente que el proyecto use el maravilloso DSL de Spring y tendrán un maravilloso en el classpath, por ejemplo. En cuyo caso esta respuesta es útil, y aunque no responde directamente al OP, será de beneficio para muchos visitantes.
Amir Abiri el
10

Usando Reflection in Java puede acceder a todos los private/publiccampos y métodos de una clase a otra. Pero según la documentación de Oracle en los inconvenientes de la sección , recomendaron que:

"Dado que la reflexión permite que el código realice operaciones que serían ilegales en el código no reflectante, como acceder a campos y métodos privados, el uso de la reflexión puede provocar efectos secundarios inesperados, que pueden hacer que el código sea disfuncional y destruir la portabilidad. Código reflectante rompe las abstracciones y, por lo tanto, puede cambiar el comportamiento con las actualizaciones de la plataforma "

a continuación se muestran los ajustes de código para demostrar los conceptos básicos de Reflection

Reflexión1.java

public class Reflection1{

    private int i = 10;

    public void methoda()
    {

        System.out.println("method1");
    }
    public void methodb()
    {

        System.out.println("method2");
    }
    public void methodc()
    {

        System.out.println("method3");
    }

}

Reflection2.java

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


public class Reflection2{

    public static void main(String ar[]) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
    {
        Method[] mthd = Reflection1.class.getMethods(); // for axis the methods 

        Field[] fld = Reflection1.class.getDeclaredFields();  // for axis the fields  

        // Loop for get all the methods in class
        for(Method mthd1:mthd)
        {

            System.out.println("method :"+mthd1.getName());
            System.out.println("parametes :"+mthd1.getReturnType());
        }

        // Loop for get all the Field in class
        for(Field fld1:fld)
        {
            fld1.setAccessible(true);
            System.out.println("field :"+fld1.getName());
            System.out.println("type :"+fld1.getType());
            System.out.println("value :"+fld1.getInt(new Reflaction1()));
        }
    }

}

Espero que ayude.

Simmant
fuente
5

Como menciona oxbow_lakes, puede usar la reflexión para sortear las restricciones de acceso (suponiendo que su SecurityManager lo permita).

Dicho esto, si esta clase está tan mal diseñada que te hace recurrir a tal piratería informática, tal vez deberías buscar una alternativa. Seguro que este pequeño truco podría ahorrarte unas horas ahora, pero ¿cuánto te costará en el futuro?

Laurence Gonsalves
fuente
3
Soy más afortunado que eso en realidad, solo estoy usando este código para extraer algunos datos, después de eso puedo arrojarlo nuevamente a la Papelera de reciclaje.
Frank Krueger
2
Bueno, en ese caso, piratea. :-)
Laurence Gonsalves
3
Esto no proporciona una respuesta a la pregunta. Para criticar o solicitar una aclaración de un autor, deje un comentario debajo de su publicación.
Mureinik
@Mureinik - Responde la pregunta con las palabras "puedes usar la reflexión". Carece de un ejemplo o una explicación mayor o cómo, pero es una respuesta. Vota abajo si no te gusta.
ArtOfWarfare
4

Utilice el marco de optimización Soot Java para modificar directamente el código de bytes. http://www.sable.mcgill.ca/soot/

Soot está completamente escrito en Java y funciona con nuevas versiones de Java.

pcpratts
fuente
3

Debes hacer lo siguiente:

private static Field getField(Class<?> cls, String fieldName) {
    for (Class<?> c = cls; c != null; c = c.getSuperclass()) {
        try {
            final Field field = c.getDeclaredField(fieldName);
            field.setAccessible(true);
            return field;
        } catch (final NoSuchFieldException e) {
            // Try parent
        } catch (Exception e) {
            throw new IllegalArgumentException(
                    "Cannot access field " + cls.getName() + "." + fieldName, e);
        }
    }
    throw new IllegalArgumentException(
            "Cannot find field " + cls.getName() + "." + fieldName);
}
Luke Hutchison
fuente
2

Si usa Spring, ReflectionTestUtils proporciona algunas herramientas útiles que ayudan aquí con un mínimo esfuerzo. Se describe como "para uso en escenarios de prueba de unidad e integración" . También hay una clase similar llamada ReflectionUtils, pero se describe como "Solo para uso interno" . Consulte esta respuesta para obtener una interpretación de lo que esto significa.

Para abordar el ejemplo publicado:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");
Steve Chambers
fuente
1
Si decide usar una clase de Utils para Spring, realmente debería usar la que no es de prueba ( docs.spring.io/spring-framework/docs/current/javadoc-api/org/… ) en su lugar, a menos que, por supuesto, usted En realidad lo estoy usando para pruebas unitarias.
Sincronización
Posiblemente, aunque esa clase se describe como "Solo para uso interno". He agregado información a la respuesta sobre esto. (He mantenido el ejemplo usando ReflectionTestUtilscomo en mi experiencia, solo he necesitado hacer este tipo de cosas en una situación de prueba).
Steve Chambers,
1

Solo una nota adicional sobre la reflexión: he observado en algunos casos especiales, cuando existen varias clases con el mismo nombre en diferentes paquetes, esa reflexión tal como se usa en la respuesta superior puede fallar al elegir la clase correcta del objeto. Entonces, si sabe cuál es el paquete.clase del objeto, entonces es mejor acceder a sus valores de campo privado de la siguiente manera:

org.deeplearning4j.nn.layers.BaseOutputLayer ll = (org.deeplearning4j.nn.layers.BaseOutputLayer) model.getLayer(0);
Field f = Class.forName("org.deeplearning4j.nn.layers.BaseOutputLayer").getDeclaredField("solver");
f.setAccessible(true);
Solver s = (Solver) f.get(ll);

(Esta es la clase de ejemplo que no funcionaba para mí)

xtof54
fuente
1

Puede usar Manifold's @JailBreak para una reflexión directa y segura de Java:

@JailBreak Foo foo = new Foo();
foo.stuffIWant = "123;

public class Foo {
    private String stuffIWant;
}

@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().stuffIWant = "123";

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

En ambos casos, el compilador resuelve el acceso de campo para que escriba de forma segura, como si fuera un campo público, mientras que Manifold genera un código de reflexión eficiente para usted bajo el capó.

Descubre más sobre Colector .

Scott
fuente
1

Es bastante fácil con la herramienta XrayInterface . Simplemente defina los captadores / establecedores que faltan, por ejemplo

interface BetterDesigned {
  Hashtable getStuffIWant(); //is mapped by convention to stuffIWant
}

y haz una radiografía de tu pobre proyecto diseñado:

IWasDesignedPoorly obj = new IWasDesignedPoorly();
BetterDesigned better = ...;
System.out.println(better.getStuffIWant());

Internamente esto se basa en la reflexión.

Corona
fuente
0

Intente solucionar el problema para el caso, la clase de la que desea establecer / obtener datos es una de sus propias clases.

Solo crea un public setter(Field f, Object value)y public Object getter(Field f)para eso. Incluso puede hacer algunas comprobaciones de seguridad por su cuenta dentro de estas funciones miembro. Por ejemplo, para el setter:

class myClassName {
    private String aString;

    public set(Field field, Object value) {
        // (A) do some checkings here  for security

        // (B) set the value
        field.set(this, value);
    }
}

Por supuesto, ahora usted tiene que averiguar la java.lang.reflect.Fieldde sStringantes del fraguado del valor del campo.

Utilizo esta técnica en un ResultSet-to-and-from-model-mapper genérico.

karldegen
fuente