Reflexión de Java: impacto de setAccessible (verdadero)

105

Estoy usando algunas anotaciones para establecer dinámicamente valores de campos en clases. Como quiero hacer esto independientemente de si es público, protegido o privado, estoy llamando setAccessible(true)al objeto Field cada vez antes de llamar al set()método. Mi pregunta es ¿qué tipo de impacto tiene la setAccessible()convocatoria en el campo mismo?

Más específicamente, digamos que es un campo privado y este conjunto de llamadas de código setAccessible(true). Si algún otro lugar en el código fuera a recuperar el mismo campo a través de la reflexión, ¿el campo ya sería accesible? ¿O los métodos getDeclaredFields()y getDeclaredField()devuelven nuevas instancias de un objeto Field cada vez?

Supongo que otra forma de plantear la pregunta es, si llamo setAccessible(true), ¿qué importancia tiene volver al valor original cuando termine?

dnc253
fuente

Respuestas:

85

Con setAccessible()usted cambia el comportamiento de AccessibleObject, es decir, la Fieldinstancia, pero no el campo real de la clase. Aquí está la documentación (extracto):

Un valor de trueindica que el objeto reflejado debe suprimir las comprobaciones del control de acceso al lenguaje Java cuando se utiliza

Y un ejemplo ejecutable:

public class FieldAccessible {
    public static class MyClass {
        private String theField;
    }

    public static void main(String[] args) throws Exception {
        MyClass myClass = new MyClass();
        Field field1 = myClass.getClass().getDeclaredField("theField");
        field1.setAccessible(true);
        System.out.println(field1.get(myClass)); // no exception
        Field field2 = myClass.getClass().getDeclaredField("theField");
        System.out.println(field2.get(myClass)); // IllegalAccessException
    }

}
Moritz Petersen
fuente
@PhilipRego, debe escribir las declaraciones de importación usted mismo. Espero que sepas cómo hacer eso.
Moritz Petersen
Problema encontrado. Tienes que lanzar o manejar NoSuchFieldException o parent.
Philip Rego
Sí, esto es solo un código de muestra. Quiero decir, throws Exceptiontambién maneja NoSuchFieldException, pero es posible que desee manejarlo de una manera más elaborada.
Moritz Petersen
Obtengo la excepción en: Field field1 = myClass.getClass (). GetDeclaredField ("theField"); por lo que ni siquiera se compila, es decir, ¿setAccessible ni siquiera importará?
user2796104
32

El getDeclaredFieldmétodo tiene que devolver un nuevo objeto cada vez, exactamente porque este objeto tiene la accessiblebandera mutable . Por tanto, no es necesario restablecer la bandera. Puede encontrar los detalles completos en esta publicación de blog .

Jörn Horstmann
fuente
3

Como han indicado otros carteles, setAccessiblesolo es aplicable a esa instancia de su java.lang.reflect.Field, por lo que no es necesario volver a configurar la accesibilidad a su estado original.

Sin embargo...

Si desea que sus llamadas field.setAccessible(true)sean persistentes, debe utilizar métodos subyacentes en java.lang.Classy java.lang.reflect.Field. Los métodos públicos le envían copias de la Fieldinstancia, por lo que se "olvida" cada vez que hace algo comoclass.getField(name)

import java.lang.reflect.*;
import sun.reflect.FieldAccessor;

public class Reflect {
    private static Method privateGetDeclaredFields;
    private static Method getFieldAccessor;

    public static Field[] fields(Class<?> clazz) throws Exception {
        return (Field[]) privateGetDeclaredFields.invoke(clazz, false);
    }

    public static <T> T get(Object instance, Field field) throws Exception {
        return ((FieldAccessor) getFieldAccessor.invoke(field, instance)).get(instance);
    }

    public static void set(Object instance, Field field, Object value) throws Exception {
        ((FieldAccessor) getFieldAccessor.invoke(field, instance)).set(instance, value);
    }

    static {
        try {
            // These are used to access the direct Field instances instead of the copies you normally get through #getDeclaredFields.
            privateGetDeclaredFields = Class.class.getDeclaredMethod("privateGetDeclaredFields", boolean.class);
            privateGetDeclaredFields.setAccessible(true);
            getFieldAccessor = Field.class.getDeclaredMethod("getFieldAccessor", Object.class);
            getFieldAccessor.setAccessible(true);
        } catch (Exception e) {
            // Should only occur if the internals change.
            e.printStackTrace();
        }
    }
}

Actualización : esta implementación es para Java 8, las versiones futuras cambian el backend que rompe esto. El mismo concepto todavía se aplica aunque realmente desee continuar con esta estrategia.

Col
fuente
-1
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class PrivateVariableAcc {

    public static void main(String[] args) throws Exception {
        PrivateVarTest myClass = new PrivateVarTest();
        Field field1 = myClass.getClass().getDeclaredField("a");
        field1.setAccessible(true);
        System.out.println("This is access the private field-"
            + field1.get(myClass));
        Method mm = myClass.getClass().getDeclaredMethod("getA");
        mm.setAccessible(true);
        System.out.println("This is calling the private method-"
            + mm.invoke(myClass, null));
    }

}
RamChandra Bhakar
fuente