为什么它不再有效
这在 Java 12 中不再起作用的原因是由于 JDK-8210522。这份企业社会责任说:
总结
核心反射具有一种过滤机制,用于从类 getXXXField(s) 和 getXXXMethod 中隐藏安全和完整性敏感字段和方法。过滤机制已用于多个版本,以隐藏安全敏感字段,如 System.security 和 Class.classLoader。
此 CSR 建议扩展筛选器,以隐藏 java.lang.reflect 和 java.lang.invoke 中许多高度安全的敏感类中的字段。
问题
java.lang.reflect 和 java.lang.invoke 包中的许多类都有私有字段,如果直接访问这些字段,将危及运行时或 VM 崩溃。理想情况下,java.base中所有非公共/非受保护的类字段都将通过核心反射进行过滤,并且不能通过不安全的API进行可读/写,但我们目前还远未达到这一点。同时,滤波机制被用作创可贴。
溶液
将筛选器扩展到以下类中的所有字段:
java.lang.ClassLoader
java.lang.reflect.AccessibleObject
java.lang.reflect.Constructor
java.lang.reflect.Field
java.lang.reflect.Method
以及 java.lang.invoke.MethodHandles.Lookup 中用于查找类和访问模式的私有字段。
规范
没有规范更改,这是对非公共/非受保护字段的过滤,java.base之外的任何内容都不应依赖这些字段。没有一个类是可序列化的。
基本上,它们会过滤掉 的字段,这样您就不会滥用它们 - 就像您当前尝试的那样。你应该找到另一种方法来做你需要的事情;尤金的答案似乎至少提供了一种选择。java.lang.reflect.Field
正确修复
删除修饰符的正确方法是检测正在运行的程序,并让代理重新定义该类。如果在首次加载类时执行此操作,则与在 JVM 启动之前修改类文件没有什么不同。换句话说,这就像修饰符从未存在过一样。final
final
解决方法
强制性警告:Java的开发人员显然不希望您在不实际更改类文件的情况下将最终字段更改为非最终字段(例如,通过重新编译源代码,检测等)。使用任何黑客攻击需要您自担风险;它可能有意想不到的副作用,只工作一段时间,和/或在未来的版本中停止工作。
用java.lang.invoke
下面使用该包。无论出于何种原因,应用于反射 API 的相同限制都不适用于 Invoke API(至少在 Java 17 之前(包括 Java 17);请继续阅读以获取更多信息)。java.lang.invoke
该示例修改类的最后一个字段。此字段通常包含一个空数组,当初始化为 容量时,该数组将在所有实例之间共享。下面将该字段设置为 ,正如您通过运行程序所看到的,这将导致列表实例包含从未添加到其中的元素。EMPTY_ELEMENTDATA
ArrayList
ArrayList
0
{"Hello", "World!"}
爪哇 12 - 17
我在Java 16.0.2和Java 17.0.3上测试了这一点,这两个版本都是从 https://adoptium.net/ 下载的。
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
public class Main {
private static final VarHandle MODIFIERS;
static {
try {
var lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
MODIFIERS = lookup.findVarHandle(Field.class, "modifiers", int.class);
} catch (IllegalAccessException | NoSuchFieldException ex) {
throw new RuntimeException(ex);
}
}
public static void main(String[] args) throws Exception {
var emptyElementDataField = ArrayList.class.getDeclaredField("EMPTY_ELEMENTDATA");
// make field non-final
MODIFIERS.set(emptyElementDataField, emptyElementDataField.getModifiers() & ~Modifier.FINAL);
// set field to new value
emptyElementDataField.setAccessible(true);
emptyElementDataField.set(null, new Object[] {"Hello", "World!"});
var list = new ArrayList<>(0);
// println uses toString(), and ArrayList.toString() indirectly relies on 'size'
var sizeField = ArrayList.class.getDeclaredField("size");
sizeField.setAccessible(true);
sizeField.set(list, 2); // the new "empty element data" has a length of 2
System.out.println(list);
}
}
使用以下命令运行代码:
javac Main.java
java --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED Main
注意:我试图使用“单源文件”功能,但这导致了一个 ConcurrentModificationException
。正如注释中指出的那样,这可能是由于一些JIT优化(例如,静态的最终字段已被内联,因为JVM不希望这样的字段能够更改)。
输出:
[Hello, World!]
Java 18+
不幸的是,上述情况会导致 Java 18.0.1 上的以下异常(从 https://adoptium.net/ 下载):
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.lang.invoke.VarForm.getMemberName(VarForm.java:114)
at Main.main(Main.java:23)
其中第 23 行是:
MODIFIERS.set(emptyElementDataField, emptyElementDataField.getModifiers() & ~Modifier.FINAL);