30

在 java8 中,可以使用例如访问类 java.lang.reflect.Fields 的字段

Field.class.getDeclaredFields();

在 java12 中(以 java9 开头?)这仅返回一个空数组。这不会改变,即使

--add-opens java.base/java.lang.reflect=ALL-UNNAMED

放。

任何想法如何实现这一目标?(除了这可能是一个坏主意的事实外,我希望能够在 junit 测试期间通过反射更改我的代码中的“静态最终”字段。通过更改“修饰符”,java8 可以做到这一点

Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(myfield, myfield.getModifiers() & ~Modifier.FINAL);

)

4

3 回答 3

40

这在 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字段ArrayListArrayList当使用容量初始化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

于 2019-05-08T14:38:02.547 回答
11

你不能。这是故意进行的更改。

例如,您可以使用PowerMock它-如果您想将其用于测试目的,@PrepareForTest它在后台使用javassist (字节码操作)。这正是评论中的错误所建议的。

换句话说,因为java-12- 无法通过 vanilla java 访问它。

于 2019-05-08T13:52:32.203 回答
7

我找到了一种方法,它适用于 JDK 8、11、17。

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;
        break;
    }
}
assertNotNull(modifiers);

使用 JDK 11 或更高版本时不要忘记设置以下参数:

--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED
于 2021-10-02T15:07:19.350 回答