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

时间:2018-01-11 00:16:13

标签: java reflection java-8 openjdk

在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

1 个答案:

答案 0 :(得分:7)

如果您希望修饰符破解(不要忘记it is a total hack),您需要在首次访问之前更改modifiers私有字段该领域。

因此,在执行f.getInt(null);之前,您需要执行以下操作:

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

原因是,无论您有多少不同的实际FieldAccessor对象,都只为类(*)的每个字段创建一个内部java.lang.reflect.Field对象。在UnsafeFieldAccessorFactory中构建FieldAccessor实现时,final修饰符的检查就完成了一次。

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

(*)禁止同步问题;作为评论中提到FieldAccessor的源代码:

  

//注意这里没有使用同步。它是正确的   (虽然效率不高)为a生成多个FieldAccessor   鉴于Field。