这在 Java 12 中不再适用的原因是JDK-8210522。该企业社会责任说:
概括
核心反射有一个过滤机制,可以从类 getXXXField(s) 和 getXXXMethod(s) 中隐藏安全性和完整性敏感字段和方法。过滤机制已在多个版本中用于隐藏安全敏感字段,例如 System.security 和 Class.classLoader。
此 CSR 建议扩展过滤器以隐藏 java.lang.reflect 和 java.lang.invoke 中许多高度安全敏感类的字段。
问题
java.lang.reflect 和 java.lang.invoke 包中的许多类都有私有字段,如果直接访问这些字段,将会危及运行时或使 VM 崩溃。理想情况下,java.base 中类的所有非公共/非受保护字段都将被核心反射过滤,并且不能通过 Unsafe 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
因此您不能滥用它们——正如您目前正在尝试做的那样。您应该找到另一种方法来做您需要的事情;尤金的回答似乎提供了至少一种选择。
注意:上述 CSR 表明最终目标是防止所有反射访问java.base
模块内的内部代码。然而,这种过滤机制似乎只影响 Core Reflection API,并且可以通过使用 Invoke API 来解决。我不确定这两个 API 是如何相关的,所以如果这不是所需的行为——除了更改静态最终字段的可疑性之外——应该有人提交错误报告(首先检查现有的)。换句话说,使用下面的技巧需要您自担风险;尝试找到另一种方法来首先做你需要的事情。
modifiers
也就是说,至少在 OpenJDK 12.0.1 中,您似乎仍然可以使用java.lang.invoke.VarHandle
.
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public final class FieldHelper {
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 makeNonFinal(Field field) {
int mods = field.getModifiers();
if (Modifier.isFinal(mods)) {
MODIFIERS.set(field, mods & ~Modifier.FINAL);
}
}
}
下面使用上面的来改变EMPTY_ELEMENTDATA
里面的静态final字段ArrayList
。ArrayList
当使用容量初始化an 时使用此字段0
。最终结果是创建的ArrayList
包含元素而没有实际添加任何元素。
import java.util.ArrayList;
public class Main {
public static void main(String[] args) throws Exception {
var newEmptyElementData = new Object[]{"Hello", "World!"};
updateEmptyElementDataField(newEmptyElementData);
var list = new ArrayList<>(0);
// toString() relies on iterator() which relies on size
var sizeField = list.getClass().getDeclaredField("size");
sizeField.setAccessible(true);
sizeField.set(list, newEmptyElementData.length);
System.out.println(list);
}
private static void updateEmptyElementDataField(Object[] array) throws Exception {
var field = ArrayList.class.getDeclaredField("EMPTY_ELEMENTDATA");
FieldHelper.makeNonFinal(field);
field.setAccessible(true);
field.set(null, array);
}
}
输出:
[Hello, World!]
根据需要使用--add-opens
。