说到反射,了解 Java 的开发者应该都听过或用过。反射被大量的开发框架所使用,有时候也会用于单元测试等场景。网上能查到的反射修改 static final 属性的方法基本从 Java 12 开始失效了,本文主要介绍一种同时适用于 Java 8 至 Java 17 的反射修改 static final 属性的方法。
方法
先说怎么做。修改 static final
属性值,关键在于通过反射将字段的 final
修饰符去掉。
Java 11 及更早版本获取 modifiers
Field 的方法:
Field modifiers = field.getClass().getDeclaredField("modifiers");
同时适用于 Java 8 至 Java 17 获取 modifiers
Field 的方法:
Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
getDeclaredFields0.setAccessible(true);
Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
Field modifiers = null;
for (Field each : fields) {
if ("modifiers".equals(each.getName())) {
modifiers = each;
}
}
代码案例:
不要忘记添加模块,自从高版本 Java 模块化之后,在 Java 12 及以上还需要指定模块
--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED
探索过程
如何修改 static final
字段的值
假设现在代码中需要修改一处 static final 属性的值,平时反射用的比较少的开发者可能直接会去网上搜怎么做。相信搜到的大部分的答案都是以下流程:
- 通过反射获取
java.lang.reflect.Field
内部的modifiers
Field,并setAccessible(true)
; - 获取修改目标字段的 Field;
- 将修改目标字段的 Field 的
modifiers
修改为非final
; - 通过修改目标字段的 Field 设置新的值,至此完成修改。
例如有一个类 SQLLogger
,里面有一个 private static final
的字段:
public class SQLLogger {
private static final Logger log = LoggerFactory.getLogger(SQLLogger.class);
}
现在要修改这个私有、静态、不可变的 log
字段,大致代码如下:
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
Field logField = SQLLogger.class.getDeclaredField("log");
logField.setAccessible(true);
modifiersField.setInt(logField, logField.getModifiers() & ~Modifier.FINAL);
logField.set(null, newValue);
报错 java.lang.NoSuchFieldException: modifiers
自从 Java 12 开始,直接获取 Field 的 modifiers
字段会得到以下错误:
Exception java.lang.NoSuchFieldException: modifiers
at Class.getDeclaredField (Class.java:2610)
难道 modifiers
字段从 Field 里面删除了?
看了一下 JDK 17 GA 的源码,modifiers 字段还是在的。
搜索了一下相关内容,不允许直接获取 Field 类中的字段,是为了避免涉及安全、敏感的字段被修改。https://bugs.openjdk.org/browse/JDK-8210522
从 jdk.internal.reflect.Reflection
第 58 行可以看到,fieldFilterMap
增加了 Field.class
的所有成员,即 Field 下的任何字段都不能直接通过公共反射方法获取。
https://github.com/openjdk/jdk/blob/jdk-17-ga/src/java.base/share/classes/jdk/internal/reflect/Reflection.java#L42-L64
难道就没有办法获取了吗?我们回到 Reflection
过滤逻辑上层调用,发现了一个方法 getDeclaredFields0
。
https://github.com/openjdk/jdk/blob/jdk-17-ga/src/java.base/share/classes/java/lang/Class.java#L3297
https://github.com/openjdk/jdk/blob/jdk-17-ga/src/java.base/share/classes/java/lang/Class.java#L3641
直接调用方法 getDeclaredFields0
,发现可以获取到想要的对象。
既然如此,我们想要 modifiers
的话,直接调用这个方法就行。这是个私有方法,通过反射调用即可。
具体实例见本文前面内容即可。