说到反射,了解 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内部的modifiersField,并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 的话,直接调用这个方法就行。这是个私有方法,通过反射调用即可。
具体实例见本文前面内容即可。
Java8到Java17:反射修改staticfinal字段值的通用方法
本文介绍了如何在Java8到Java17之间通过反射来修改staticfinal字段的值,重点是通过访问私有的modifiersField并去除final修饰,从而实现修改。这种方法适用于Java11及更早版本,以及Java12及以上版本,需要考虑Java模块化的添加-opens选项。文章还探讨了由于安全原因,从Java12开始直接获取modifiersField不再可行的背景和解决策略。
1997

被折叠的 条评论
为什么被折叠?



