说到反射,了解 Java 的开发者应该都听过或用过。反射被大量的开发框架所使用,有时候也会用于单元测试等场景。网上能查到的反射修改 static final 属性的方法基本从 Java 12 开始失效了,本文主要介绍一种同时适用于 Java 8 至 Java 17 的反射修改 static final 属性的方法。

文章目录

  • 方法
  • 探索过程
  • 如何修改 `static final` 字段的值
  • 报错 java.lang.NoSuchFieldException: modifiers

方法

先说怎么做。修改 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反射设置static_Java反射设置

代码案例:

探索过程

如何修改 static final 字段的值

假设现在代码中需要修改一处 static final 属性的值,平时反射用的比较少的开发者可能直接会去网上搜怎么做。相信搜到的大部分的答案都是以下流程:

  1. 通过反射获取 java.lang.reflect.Field 内部的 modifiers Field,并 setAccessible(true)
  2. 获取修改目标字段的 Field;
  3. 将修改目标字段的 Field 的 modifiers 修改为非 final
  4. 通过修改目标字段的 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 字段还是在的。

Java反射设置 java反射设置static_jvm_02

搜索了一下相关内容,不允许直接获取 Field 类中的字段,是为了避免涉及安全、敏感的字段被修改。https://bugs.openjdk.org/browse/JDK-8210522

jdk.internal.reflect.Reflection 第 58 行可以看到,fieldFilterMap 增加了 Field.class 的所有成员,即 Field 下的任何字段都不能直接通过公共反射方法获取。

Java反射设置 java反射设置static_开发语言_03

难道就没有办法获取了吗?我们回到 Reflection 过滤逻辑上层调用,发现了一个方法 getDeclaredFields0

Java反射设置 java反射设置static_jvm_04

Java反射设置 java反射设置static_jvm_05

直接调用方法 getDeclaredFields0,发现可以获取到想要的对象。

Java反射设置 java反射设置static_Java反射设置_06

既然如此,我们想要 modifiers 的话,直接调用这个方法就行。这是个私有方法,通过反射调用即可。

具体实例见本文前面内容即可。