Java 设置静态最终字段的反射在上一次反射后失败

Java reflection to set static final field fails after previous reflection

在 Java 中,事实证明字段访问器被缓存,使用访问器有副作用。例如:

class A {
    private static final int FOO = 5;
}

Field f = A.class.getDeclaredField("FOO");
f.setAccessible(true);
f.getInt(null); // succeeds

Field mf = Field.class.getDeclaredField("modifiers" );
mf.setAccessible(true);

f = A.class.getDeclaredField("FOO");
f.setAccessible(true);
mf.setInt(f, f.getModifiers() & ~Modifier.FINAL);
f.setInt(null, 6); // fails

class A {
    private static final int FOO = 5;
}

Field mf = Field.class.getDeclaredField("modifiers" );
mf.setAccessible(true);

f = A.class.getDeclaredField("FOO");
f.setAccessible(true);
mf.setInt(f, f.getModifiers() & ~Modifier.FINAL);
f.setInt(null, 6); // succeeds

这是失败的堆栈跟踪的相关位:

java.lang.IllegalAccessException: Can not set static final int field A.FOO to (int)6
    at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
    at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:100)
    at sun.reflect.UnsafeQualifiedStaticIntegerFieldAccessorImpl.setInt(UnsafeQualifiedStaticIntegerFieldAccessorImpl.java:129)
    at java.lang.reflect.Field.setInt(Field.java:949)

这两个反射访问当然发生在我的代码库的不同部分,我真的不想更改第一个来修复第二个。有什么方法可以更改第二个反射访问以确保它在两种情况下都成功吗?

我试着查看 Field 对象,但它没有任何看起来有用的方法。在调试器中,我注意到 overrideFieldAccessor 设置在第一个示例中返回的第二个 Field 上,并且看不到修饰符的更改。不过,我不确定该怎么做。

如果有影响,我正在使用 openjdk-8

如果你想让修饰符 hack(不要忘记 it is a total hack)起作用,你需要在第一个 之前更改 modifiers 私有字段您访问该字段的时间。

所以,在你f.getInt(null);之前,你需要做:

mf.setInt(f, f.getModifiers() & ~Modifier.FINAL);

原因是,无论您有多少个不同的实际 java.lang.reflect.Field 对象,都只会为 class (*) 的每个字段创建一个内部 FieldAccessor 对象。并且在 UnsafeFieldAccessorFactory.

中构造 FieldAccessor 实现时,对 final 修饰符的检查完成一次

当确定您无法访问 final static 字段时(因为 setAccessible 覆盖不起作用,但非静态最终字段,但不适用于 static 最终字段),即使通过不同的 Field 对象,它也会在每次后续反射中不断失败,因为它一直使用相同的 FieldAccessor.

(*) 排除同步问题;作为 Field 的源代码,在评论中提到:

// NOTE that there is no synchronization used here. It is correct (though not efficient) to generate more than one FieldAccessor for a given Field.