Réflexion Java - impact de setAccessible (true)

106

J'utilise des annotations pour définir dynamiquement les valeurs des champs dans les classes. Puisque je veux faire cela indépendamment du fait que ce soit public, protégé ou privé, je fais un appel setAccessible(true)sur l'objet Field à chaque fois avant d'appeler la set()méthode. Ma question est de savoir quel genre d'impact cet setAccessible()appel a-t-il sur le terrain lui-même?

Plus précisément, disons qu'il s'agit d'un champ privé et que cet ensemble d'appels de code setAccessible(true). Si un autre endroit du code devait alors récupérer le même champ par réflexion, le champ serait-il déjà accessible? Ou les méthodes getDeclaredFields()and getDeclaredField()renvoient-elles à chaque fois de nouvelles instances d'un objet Field?

Je suppose qu'une autre façon de poser la question est de savoir si j'appelle setAccessible(true), à quel point est-il important de le remettre à sa valeur d'origine après avoir terminé?

dnc253
la source

Réponses:

85

Avec setAccessible()vous modifiez le comportement de AccessibleObject, c'est-à-dire l' Fieldinstance, mais pas le champ réel de la classe. Voici la documentation (extrait):

Une valeur de trueindique que l'objet réfléchi doit supprimer les vérifications du contrôle d'accès en langage Java lorsqu'il est utilisé

Et un exemple exécutable:

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
la source
@PhilipRego vous devez écrire vous-même les déclarations d'importation. J'espère que vous savez comment faire cela.
Moritz Petersen
Problème détecté. Vous devez lancer ou gérer NoSuchFieldException ou parent.
Philip Rego
Ouais, ce n'est qu'un exemple de code. Je veux dire, throws Exceptiongère également NoSuchFieldException, mais vous voudrez peut-être le gérer d'une manière plus élaborée.
Moritz Petersen
J'obtiens l'exception sur le: Field field1 = myClass.getClass (). GetDeclaredField ("theField"); donc il ne compile même pas, c'est-à-dire que setAccessible n'aura même pas d'importance?
user2796104
32

La getDeclaredFieldméthode doit retourner un nouvel objet à chaque fois, exactement parce que cet objet a l' accessibleindicateur mutable . Il n'est donc pas nécessaire de réinitialiser le drapeau. Vous pouvez trouver tous les détails dans cet article de blog .

Jörn Horstmann
la source
3

Comme d'autres affiches l'ont indiqué, setAccessiblene s'applique qu'à cette instance de votre java.lang.reflect.Field, il n'est donc pas nécessaire de rétablir l'accessibilité à son état d'origine.

Toutefois...

Si vous souhaitez que vos appels field.setAccessible(true)soient persistants, vous devez utiliser des méthodes sous-jacentes dans java.lang.Classet java.lang.reflect.Field. Les méthodes accessibles au public vous envoient des copies de l' Fieldinstance, de sorte qu'elle «oublie» chaque fois que vous faites quelque chose commeclass.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();
        }
    }
}

Mise à jour : Cette implémentation est pour Java 8, les futures versions changent le backend qui rompt cela. Le même concept s'applique toujours si vous souhaitez vraiment continuer cette stratégie.

Col-E
la source
-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
la source