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

Get declared fields of java.lang.reflect.Fields in jdk12

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

Field.class.getDeclaredFields();

在java12中(以java9开头?)这个returns只是一个空数组。即使

这也不会改变
--add-opens java.base/java.lang.reflect=ALL-UNNAMED

设置。

有什么想法可以实现吗? (事实上​​,这可能是个坏主意,我希望能够在 junit 测试期间通过反射更改代码中的 "static final" 字段。通过更改 java8 java8 "modifiers"

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

)

你不能。这是有意为之的改变。

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

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

这在 Java 12 中不再有效的原因是 JDK-8210522。此 CSR 表示:

Summary

Core reflection has a filtering mechanism to hide security and integrity sensitive fields and methods from Class getXXXField(s) and getXXXMethod(s). The filtering mechanism has been used for several releases to hide security sensitive fields such as System.security and Class.classLoader.

This CSR proposes to extend the filters to hide fields from a number of highly security sensitive classes in java.lang.reflect and java.lang.invoke.

Problem

Many of classes in java.lang.reflect and java.lang.invoke packages have private fields that, if accessed directly, will compromise the runtime or crash the VM. Ideally all non-public/non-protected fields of classes in java.base would be filtered by core reflection and not be readable/writable via the Unsafe API but we are no where near this at this time. In the mean-time the filtering mechanism is used as a band aid.

Solution

Extend the filter to all fields in the following classes:

java.lang.ClassLoader
java.lang.reflect.AccessibleObject
java.lang.reflect.Constructor
java.lang.reflect.Field
java.lang.reflect.Method

and the private fields in java.lang.invoke.MethodHandles.Lookup that are used for the lookup class and access mode.

Specification

There are no specification changes, this is filtering of non-public/non-protected fields that nothing outside of java.base should rely on. None of the classes are serializable.

基本上,它们会过滤掉 java.lang.reflect.Field 的字段,因此您不能滥用它们 — 正如您目前正在尝试做的那样。你应该找到另一种方法来做你需要做的事情; 似乎提供了至少一种选择。


:上述CSR表明最终目标是防止对java.base模块内部代码的所有反射访问。然而,这种过滤机制似乎只影响核心反射 API,并且可以通过使用调用 API 来解决。我不确定这两个 API 是如何相关的,所以如果这不是期望的行为——除了更改静态最终字段的可疑性之外——有人应该 submit a bug report(检查现有的第一的)。换句话说,使用下面的技巧需要您自担风险;首先尝试找到另一种方法来完成您需要的工作。


也就是说,看起来您仍然可以侵入 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);
        }
    }

}

下面使用上面的方法来改变ArrayList里面的static final EMPTY_ELEMENTDATA字段。当 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

我找到了一种方法并且它在 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

This 适用于 JDK 17.

import java.lang.reflect.Field;
import sun.misc.Unsafe;

/**
 * @author Arnah
 * @since Feb 21, 2021
 **/
public class FieldUtil{

    private static Unsafe unsafe;

    static{
        try{
            final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
            unsafeField.setAccessible(true);
            unsafe = (Unsafe) unsafeField.get(null);
        }catch(Exception ex){
            ex.printStackTrace();
        }
    }

    public static void setFinalStatic(Field field, Object value) throws Exception{
        Object fieldBase = unsafe.staticFieldBase(field);
        long fieldOffset = unsafe.staticFieldOffset(field);

        unsafe.putObject(fieldBase, fieldOffset, value);
    }
}
public class YourClass{
    public static final int MAX_ITEM_ROWS = 35_000;
}

FieldUtil.setFinalStatic(YourClass.class.getDeclaredField("MAX_ITEM_ROWS"), 1);