Reputation: 972
In Java, it turns out that field accessors get cached, and using accessors has side-effects. For example:
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
whereas
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
Here's the relevant bit of the stack trace for the failure:
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)
These two reflective accesses are of course happening in very different parts of my code base, and I don't really want to change the first to fix the second. Is there any way to change the second reflective access to ensure it succeeds in both cases?
I tried looking at the Field
object, and it doesn't have any methods that seem like they would help. In the debugger, I noticed overrideFieldAccessor
is set on the second Field
returned in the first example and doesn't see the changes to the modifiers. I'm not sure what to do about it, though.
If it makes a difference, I'm using openjdk-8
.
Upvotes: 4
Views: 10421
Reputation: 31279
If you want the modifier hack (don't forget it is a total hack) to work, you need to change the modifiers
private field before the first time you access the field.
So, before you do f.getInt(null);
, you need to do:
mf.setInt(f, f.getModifiers() & ~Modifier.FINAL);
The reason is that only one internal FieldAccessor
object is created for each field of a class (*), no matter how many different actual java.lang.reflect.Field
objects you have. And the check for the final
modifier is done once when it constructs the FieldAccessor implementation in the UnsafeFieldAccessorFactory
.
When it is determined you can't access final static
fields (because, the setAccessible
override doesn't works but non-static final fields, but not for static
final fields), it will keep failing for every subsequent reflection, even through a different Field
object, because it keeps using the same FieldAccessor
.
(*) barring synchronization issues; as the source code for Field
mentions in a comment:
// NOTE that there is no synchronization used here. It is correct (though not efficient) to generate more than one FieldAccessor for a given Field.
Upvotes: 11