Java反射 - setAccessible(true)的影响

143

我正在使用一些注释来动态设置类中字段的值。由于我想不管其访问级别(public,protected或private),都要执行此操作,因此在调用set()方法之前,每次都会对Field对象调用setAccessible(true)。那么问题来了,setAccessible()调用对该字段本身有什么影响?

更具体地说,假设它是一个私有字段,这组代码调用了setAccessible(true)。如果代码中的其他地方随后通过反射检索相同的字段,那么该字段是否已经可访问?还是getDeclaredFields()getDeclaredField()方法每次返回Field对象的新实例?

我猜另一种陈述问题的方式是,如果我调用了setAccessible(true),那么将其恢复为原始值结束时有多重要?

4个回答

130

setAccessible() 方法改变 AccessibleObject 的行为,即 Field 实例的行为,但不会改变类的实际字段。这是文档的摘录:

当设置为 true 时,表示反射对象在使用时应该抑制对Java语言访问控制的检查。

以下是一个可运行的示例:

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
    }

}

@PhilipRego 你需要自己编写导入声明。希望你知道如何做。 - Moritz Petersen
发现问题。您必须抛出或处理NoSuchFieldException或其父类。 - Philip Rego
2
是的,这只是示例代码。我的意思是,throws Exception 也可以处理 NoSuchFieldException,但你可能想以更详细的方式处理它。 - Moritz Petersen
我在以下代码行遇到了异常: Field field1 = myClass.getClass().getDeclaredField("theField"); 因此,它甚至无法编译,即setAccessible也无关紧要? - Hmbucker

44
< p > getDeclaredField方法每次都必须返回一个新对象,正是因为该对象具有可变的accessible标志。因此,没有必要重置该标志。您可以在此博客文章中找到完整的详细信息。


8
正如其他帖子所指出的那样,"setAccessible" 只适用于您的 "java.lang.reflect.Field" 实例,因此不需要将可访问性设置回其原始状态。
但是...
如果您想要使调用 "field.setAccessible(true)" 持久化,则需要使用 "java.lang.Class" 和 "java.lang.reflect.Field" 中的底层方法。公共方法会向您发送 "Field" 实例的 "副本",因此每次执行类似于 "class.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();
        }
    }
}

更新:此实现适用于Java 8,未来版本会更改后端,从而破坏此实现。然而,如果您确实希望继续使用这种策略,仍然适用相同的概念。


-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));
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接