Java反射 - 如果前面带有“get”操作,则“set”上的IllegalAccessException

问题描述 投票:2回答:1

我试图用反射改变private static final字段的值(是的,这可能是一个非常糟糕的想法,我知道)。而且,在大多数情况下,使用以下代码可以正常工作:

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class A {

    public static void main(String[] args) throws ReflectiveOperationException {
        System.out.println("Before :: " + B.get());
        Field field = B.class.getDeclaredField("arr");
        field.setAccessible(true);
        // System.out.println("Peek   :: " + ((String[]) field.get(null))[0]);
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, new String[] { "Good bye, World!" });
        System.out.println("After  :: " + B.get());
    }
}

class B {
    private static final String[] arr = new String[] { "Hello, World!" };

    public static String get() {
        return arr[0];
    }
}

按预期打印:

Before :: Hello, World!
After  :: Good bye, World!

当我在getting之前通过反射尝试set字段值时出现问题。也就是说,如果我取消注释上面示例中的注释行,我会得到以下内容:

Before :: Hello, World!
Peek   :: Hello, World!
Exception in thread "main" java.lang.IllegalAccessException: Can not set static final [Ljava.lang.String; field B.arr to [Ljava.lang.String;
        at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
        at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)
        at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)
        at java.lang.reflect.Field.set(Field.java:764)
        at A.main(A.java:14)

为什么会这样?在打电话给accessible之后我试图再次设置get旗帜,但它没有帮助。我尝试了许多其他似乎没有帮助的事情......

谢谢你的帮助!


编辑:Using reflection to change static final File.separatorChar for unit testing?中有一个答案元素(参见@Rogério的“重要更新”)。

重要更新:上述解决方案并非在所有情况下都有效。如果在重置之前可以访问该字段并通​​过Reflection读取,则会抛出IllegalAccessException。它失败是因为Reflection API创建了内部FieldAccessor对象,这些对象被缓存并重用(请参阅java.lang.reflect.Field#acquireFieldAccessor(boolean)实现)。示例测试代码失败:

Field f = File.class.getField("separatorChar"); f.setAccessible(true); f.get(null);
// call setFinalStatic as before: throws IllegalAccessException

可悲的是,它没有说明如何解决这个问题......我如何“重置”这个领域?

java reflection
1个回答
2
投票

A very bad idea, indeed.

在回答这个问题时,我不确定应该对细节有多深入。但这里有一个简短的总结:

当您执行反射性Field#get调用时,调用将在内部(在几次安全检查之后)委派给sun.reflect.FieldAccessor。这是一个内部接口,顾名思义,它提供对字段值的访问。内部使用的FieldAccessor实例是懒惰创建的,“缓存”供以后使用,甚至在多个Field实例之间共享。

FieldAccessor接口有许多不同的实现。这些实现专门用于通过调用setAccessible(true)可访问的普通字段,静态字段或私有字段的各种情况。例如,在你的情况下,有一个UnsafeQualifiedStaticObjectFieldAccessorImpl涉及,名称已经表明这只是几十个专业化之一。

许多这些FieldAccessor实现存储了一个内部状态,它描述了该字段的一些属性。例如,字段是“只读”还是final(!)。

重点是:当您进行反射性FieldAccessor调用时创建的Field#get与稍后用于反射性Field#set调用的相同。但是当FieldAccessor被创建时,该领域仍然被认为是final。您只有在创建FieldAccessor后才能更改此设置。

因此,最简单的解决方案是确保在执行第一次反射调用之前更改字段的final状态。这样,内部创建的FieldAccessor就是在“非最终”字段上运行的import java.lang.reflect.Field; import java.lang.reflect.Modifier; public class A { public static void main(String[] args) throws Exception { System.out.println("Before :: " + B.get()); Field field = B.class.getDeclaredField("arr"); field.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); System.out.println("Peek :: " + ((String[]) field.get(null))[0]); field.set(null, new String[] { "Good bye, World!" }); System.out.println("After :: " + B.get()); } } class B { private static final String[] arr = new String[] { "Hello, World!" }; public static String get() { return arr[0]; } }

brutallyHackYourWayThroughInternalClasses

我不应该提这个。人们会这样做。但无论如何:

从技术上讲,也可以FieldAccessor并修改内部创建的final,以便之后允许修改字段,即使它最初是为字段的import java.lang.reflect.Field; import java.lang.reflect.Modifier; public class A { public static void main(String[] args) throws Exception { System.out.println("Before :: " + B.get()); Field field = B.class.getDeclaredField("arr"); field.setAccessible(true); System.out.println("Peek :: " + ((String[]) field.get(null))[0]); brutallyHackYourWayThroughInternalClasses(field); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); field.set(null, new String[] { "Good bye, World!" }); System.out.println("After :: " + B.get()); } private static void brutallyHackYourWayThroughInternalClasses(Field field) throws Exception { Field overrideFieldAccessorField = Field.class.getDeclaredField("overrideFieldAccessor"); overrideFieldAccessorField.setAccessible(true); Object overrideFieldAccessorValue = overrideFieldAccessorField.get(field); Class<?> unsafeFieldAccessorImplClass = Class.forName("sun.reflect.UnsafeFieldAccessorImpl"); Field isFinalField = unsafeFieldAccessorImplClass.getDeclaredField("isFinal"); isFinalField.setAccessible(true); isFinalField.set(overrideFieldAccessorValue, false); Class<?> unsafeQualifiedStaticFieldAccessorImplClass = Class.forName("sun.reflect.UnsafeQualifiedStaticFieldAccessorImpl"); Field isReadOnlyField = unsafeQualifiedStaticFieldAccessorImplClass.getDeclaredField( "isReadOnly"); isReadOnlyField.setAccessible(true); isReadOnlyField.set(overrideFieldAccessorValue, false); } } class B { private static final String[] arr = new String[] { "Hello, World!" }; public static String get() { return arr[0]; } } 版本创建的:

qazxswpoi
© www.soinside.com 2019 - 2024. All rights reserved.