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

Hen*_*ing 14 java reflection unit-testing java-12

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

Field.class.getDeclaredFields();
Run Code Online (Sandbox Code Playgroud)

在java12中(从java9开始),这仅返回一个空数组。即使有

--add-opens java.base/java.lang.reflect=ALL-UNNAMED
Run Code Online (Sandbox Code Playgroud)

组。

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

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

Wu *_*jie 16

我找到了一种方法,它可以在 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);
Run Code Online (Sandbox Code Playgroud)

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

--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED
Run Code Online (Sandbox Code Playgroud)

  • 停止使用 JDK 18。此答案的代码仍然有效,因为您可以获得可以操作的“modifiers”字段的“Field”实例,但它不会为您提供对不可修改的最终字段的写访问权限。 (3认同)

Sla*_*law 13

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

摘要

核心反射具有一种过滤机制,可从类getXXXField和getXXXMethod隐藏安全性和完整性敏感的字段和方法。筛选机制已用于多个版本中,以隐藏对安全敏感的字段,例如System.security和Class.classLoader。

此CSR建议扩展过滤器,以隐藏java.lang.reflect和java.lang.invoke中许多高度安全敏感类的字段。

问题

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
Run Code Online (Sandbox Code Playgroud)

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.

Basically, they filter out the fields of java.lang.reflect.Field so you can't abuse them—as you're currently trying to do. You should find another way to do what you need; the answer by Eugene appears to provide at least one option.


Note: The above CSR indicates the ultimate goal is to prevent all reflective access to internal code within the java.base module. This filtering mechanism seems to only affect the Core Reflection API, however, and can be worked around by using the Invoke API. I'm not exactly sure how the two APIs are related, so if this isn't desired behavior—beyond the dubiousness of changing a static final field—someone should submit a bug report (check for an existing one first). In other words, use the below hack at your own risk; try to find another way to do what you need first.


That said, it looks like you can still hack into the modifiers field, at least in OpenJDK 12.0.1, using 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);
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

The following uses the above to change the static final EMPTY_ELEMENTDATA field inside ArrayList. This field is used when an ArrayList is initialized with a capacity of 0. The end result is the created ArrayList contains elements without having actually added any elements.

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);
    }

}
Run Code Online (Sandbox Code Playgroud)

Output:

java.lang.ClassLoader
java.lang.reflect.AccessibleObject
java.lang.reflect.Constructor
java.lang.reflect.Field
java.lang.reflect.Method
Run Code Online (Sandbox Code Playgroud)

Use --add-opens as necessary.

  • 这种方法可能随时会中断,因此最好不要依赖它。通常,没有人应该期望能够更改静态的final字段。对于模拟和其他测试工具的长期建议是使用代理并删除要模拟的字段的最终修饰符。 (7认同)
  • 我会这样表述:“*您无法通过反射删除`final`修饰符;删除它的唯一方法是实际更改类*”。无论您是在编译后更改持久类文件还是通过 Instrumentation 即时更改它们,都没有关系;无论哪种情况,您实际上都是在更改类,而不是修补一些反射工件。我深入研究了 JDK 18 代码,有趣的一点是,即使以这种方式更改修饰符字段有效,它也不会对修改该字段的能力产生影响。 (4认同)
  • 调用包的一般模式似乎不执行与反射相同的检查,例如,您可以毫无问题地实例化新的枚举常量。顺便说一句,只要您的代码属于未命名模块,您甚至可以通过插入 `Module java_base = Field.class.getModule(), unnamed = FieldHelper.class.getModule(); 以编程方式消除警告。java_base.addOpens("java.lang.reflect", 未命名); java_base.addOpens("java.util", unnamed);` 在 `FieldHelper` 初始化程序的开头。 (3认同)
  • 当我们考虑加载时检测时,即使在启动 JVM 之前修改类文件也没有什么不同,因此[二进制兼容性约束](https://docs.oracle.com/javase/specs/jls/se17/html/ jls-13.html#jls-13.4.9-200) 适用。除非“final”字段也是编译时常量,否则它可以通过检测来工作。 (3认同)
  • 我想,当您使用单源文件功能时,“ArrayList”已经被使用得太多了(在动态编译示例时),“static final”字段访问已由 JIT 优化,因此反射 hack 对此字段没有影响,而“size”字段的更改在规范范围内(某种程度),因此仍然完成。大小为 2 但长度为零的数组(已内联的原始引用)是并发修改的指示符。始终如一,当您使用“System.out.println(Arrays.toString(list.toArray()));”时,您会得到“[null, null]”。 (3认同)

kya*_*kya 9

适用于 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);
    }
}
Run Code Online (Sandbox Code Playgroud)
public class YourClass{
    public static final int MAX_ITEM_ROWS = 35_000;
}

FieldUtil.setFinalStatic(YourClass.class.getDeclaredField("MAX_ITEM_ROWS"), 1);
Run Code Online (Sandbox Code Playgroud)

  • 1) `public static final int MAX_ITEM_ROWS = 35_0​​00;` 是一个编译时常量。在读取“MAX_ITEM_ROWS”的每个位置,常量值“35_000”已在编译时插入。2)当然,当您的示例尝试将字段的值设置为“35_000”时,您不会注意到它已经具有相同的值。当然,当“不改变”就被认为是成功时,到处都会看起来是成功的。3)如果您能够读取您写入的值,您会注意到这段代码严重失败。您正在将*对象引用*设置为*int*变量。只有“不安全”才能使之成为可能…… (9认同)

Eug*_*ene 7

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

例如,你可以使用PowerMock它的@PrepareForTest-引擎盖下它使用了Javassist如果你想利用它来进行测试目的(字节码操作)。这正是注释中的错误建议采取的措施。

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