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

Ell*_*ael 3 java reflection openjdk java-8

在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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

这两个反射访问当然发生在我的代码库的非常不同的部分,我真的不想改变第一个来修复第二个.有没有办法改变第二次反射访问以确保它在两种情况下都成功?

我试着查看Field对象,它没有任何看起来像他们会帮助的方法.在调试器中,我注意到在第一个示例中返回overrideFieldAccessor的第二个上设置,Field并且没有看到修改器的更改.不过,我不知道该怎么办.

如果它有所作为,我正在使用openjdk-8.

Erw*_*idt 8

如果你想修改器hack(不要忘记它是一个完全的黑客)工作,你需要在第一次访问该字段之前更改modifiers私有字段.

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

mf.setInt(f, f.getModifiers() & ~Modifier.FINAL);
Run Code Online (Sandbox Code Playgroud)

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

当确定你不能访问final static字段时(因为,setAccessible覆盖不起作用,但非静态的最终字段,但不适用于static最终字段),它将对每个后续反射都失败,即使是通过不同的Field对象,因为它继续使用相同的FieldAccessor.

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

//注意这里没有使用同步.为给定的字段生成多个FieldAccessor是正确的(尽管效率不高).

  • 您可以使用`static final int FOO = Math.abs(5);`来防止编译时优化而不创建`Integer`对象.或者``static final int FOO; 静态{FOO = 5; }`.但请记住,这不会阻止运行时优化.原则上,允许JVM积极地优化"静态最终"字段,而不需要遵守JLS不允许的反射操作.我强烈反对这样做; 如果没有更清洁,更简单的替代方案,就没有可想象的场景. (2认同)