Reflexão de Java - impacto de setAccessible (true)

106

Estou usando algumas anotações para definir dinamicamente os valores dos campos nas classes. Como desejo fazer isso independentemente de ser público, protegido ou privado, estou sempre chamando setAccessible(true)o objeto Field antes de chamar o set()método. Minha pergunta é que tipo de impacto a setAccessible()chamada tem no campo em si?

Mais especificamente, digamos que seja um campo privado e este conjunto de chamadas de código setAccessible(true). Se algum outro lugar no código fosse então recuperar o mesmo campo por meio de reflexão, o campo já estaria acessível? Ou os métodos getDeclaredFields()e getDeclaredField()retornam novas instâncias de um objeto Field a cada vez?

Acho que outra forma de colocar a questão é: se eu ligar setAccessible(true), qual a importância de voltar ao valor original depois de terminar?

dnc253
fonte

Respostas:

85

Com setAccessible()você altera o comportamento do AccessibleObject, ou seja, a Fieldinstância, mas não o campo real da classe. Aqui está a documentação (trecho):

Um valor de trueindica que o objeto refletido deve suprimir verificações de controle de acesso da linguagem Java quando ele é usado

E um exemplo executável:

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
fonte
@PhilipRego, você mesmo precisa escrever as declarações de importação. Espero que você saiba como fazer isso.
Moritz Petersen
Problema encontrado. Você tem que lançar ou manipular NoSuchFieldException ou pai.
Philip Rego
Sim, este é apenas um código de amostra. Quer dizer, throws Exceptiontambém controla NoSuchFieldException, mas você pode querer lidar com isso de uma forma mais elaborada.
Moritz Petersen
Estou recebendo a exceção em: Field field1 = myClass.getClass (). GetDeclaredField ("theField"); então nem mesmo compila, ou seja, setAccessible não importa?
user2796104
32

O getDeclaredFieldmétodo deve retornar um novo objeto a cada vez, exatamente porque esse objeto possui o accessibleflag mutável . Portanto, não há necessidade de redefinir o sinalizador. Você pode encontrar os detalhes completos nesta postagem do blog .

Jörn Horstmann
fonte
3

Como outros pôsteres indicaram, setAccessibleé aplicável apenas a essa instância do seu java.lang.reflect.Field, portanto, não é necessário definir a acessibilidade de volta ao seu estado original.

Contudo...

Se você deseja que suas chamadas field.setAccessible(true)sejam persistentes, você precisa usar métodos subjacentes em java.lang.Classe java.lang.reflect.Field. Os métodos voltados para o público enviam cópias da Fieldinstância, então ela "esquece" cada vez que você faz 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();
        }
    }
}

Atualização : esta implementação é para Java 8, versões futuras mudam o backend que quebra isso. O mesmo conceito ainda se aplica, se você realmente quiser continuar com essa estratégia.

Col-E
fonte
-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
fonte