获取jdk12中java.lang.reflect.Fields的声明字段

时间:2019-05-08 11:06:26

标签: java unit-testing reflection java-12

在java8中,可以使用例如

来访问类java.lang.reflect.Fields的字段。
Field.class.getDeclaredFields();

在java12中(从java9开始),这仅返回一个空数组。甚至没有改变

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

设置。

任何想法如何实现这一目标? (Appart认为这可能不是一个好主意,我希望能够通过反射在junit测试期间更改代码中的“ static final”字段。Java8可以通过更改“修饰符”来实现

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

2 个答案:

答案 0 :(得分:8)

在Java 12中不再起作用的原因是由于JDK-8210522。该企业社会责任说:

  

摘要

     

核心反射具有一种过滤机制,可从类getXXXField和getXXXMethod隐藏安全性和完整性敏感的字段和方法。过滤机制已用于多个版本中,以隐藏对安全敏感的字段,例如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的字段,因此您不会滥用它们-正如您正在尝试的那样。您应该找到另一种方法来满足您的需求。 answer by Eugene似乎提供了至少一个选项。


注意:以上CSR指示最终目标是阻止java.base模块内的所有内部代码反射访问。但是,此过滤机制似乎仅影响Core Reflection API,并且可以使用Invoke API来解决。我不确定这两个API之间的关系,因此,如果这不是人们所期望的行为,那么除了更改静态final字段的可疑性之外,还应该有人submit a bug report(首先检查是否存在一个)。换句话说,使用以下黑客程序后果自负;尝试找到另一种方法来做您首先需要做的事情。


也就是说,您似乎仍然可以使用modifiers进入java.lang.invoke.VarHandle字段,至少在OpenJDK 12.0.1中是如此。

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内的静态最终ArrayList字段。初始化ArrayList的容量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

答案 1 :(得分:6)

不能。这是有意进行的更改。

例如,您可以使用PowerMock并且它是@PrepareForTest-如果您想将其用于测试目的,则可以使用javassist(字节码操作)。这正是注释中的错误建议采取的措施。

换句话说,由于java-12-无法通过香草java进行访问。