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

Posted

技术标签:

【中文标题】获取jdk12中java.lang.reflect.Fields的声明字段【英文标题】:Get declared fields of java.lang.reflect.Fields in jdk12 【发布时间】:2019-09-26 02:30:47 【问题描述】:

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

)

【问题讨论】:

我建议您退后一步,仔细考虑您的整个设置。你为什么要首先改变 static 字段? @Lino 这是因为我需要用“可测试的”记录器实现替换记录器。我很确定还有其他方法可以解决这个问题,但我想了解发生了什么变化,为什么以及在哪里可能会记录下来...... IMO 你不应该测试记录器。但真正的问题仍然有效。 这似乎是变化:bugs.openjdk.java.net/browse/JDK-8210496 Come in JDK 12 您仍然可以通过sun.misc.Unsafe 修改最终字段,但顾名思义,它可能会破坏一切。 【参考方案1】:

我找到了一种方法,它适用于 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

【讨论】:

很好,这确实适用于 JDK17!这允许我们将项目升级到 JDK17,并解决错误 - 直到我们的修复正式集成到 JDK 中。【参考方案2】:

这在 Java 12 中不再适用的原因是 JDK-8210522。该 CSR 表示:

总结

核心反射具有过滤机制,可以从类 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 的字段,因此您不能滥用它们——正如您目前正在尝试做的那样。您应该找到另一种方法来做您需要的事情; answer by Eugene 似乎提供了至少一个选项。


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


也就是说,您似乎仍然可以使用 java.lang.invoke.VarHandle 侵入 modifiers 字段,至少在 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);
        
    


下面使用上面的方法来改变ArrayList内部的静态最终EMPTY_ELEMENTDATA字段。当ArrayList0 的容量初始化时使用此字段。最终结果是创建的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

【讨论】:

这种方法随时可能失效,所以最好不要依赖它。一般来说,没有人应该期望能够更改静态最终字段。对于模拟和其他测试工具的长期建议是使用代理并删除要模拟的字段的最终修饰符。 所以...作为一名非 Java 开发人员:“如何将这个超流行答案中列出的类的‘扩展过滤器扩展到所有字段’?”。如何以及在哪里?我应该如何处理提供的课程列表才能到达应许之地?我刚刚收到了一个遗留库,我应该在其中更改一个字符串并构建一个新的 jar 文件。由于这个问题,所有测试似乎都失败了。 “java.lang.RuntimeException:java.lang.NoSuchFieldException:修饰符” @MrMambo007 我的回答只是为了表明VarHandle 至少目前可以用来代替反射来访问“禁止”内部。如何将其集成到他们的应用程序中高度依赖于应用程序。我只能建议您跟踪堆栈跟踪到您的库使用反射的位置,看看您是否可以切换到VarHandle,而对其余代码的干扰最小。假设几乎没有代码重复和足够的抽象,那么更改应该不会太难。如果您需要更具体的帮助,请考虑提出一个新问题。 似乎调用包的一般模式没有执行与反射相同的检查,例如您可以毫无问题地实例化新的枚举常量。顺便说一句,只要您的代码属于未命名的模块,您甚至可以通过在FieldHelper 的初始化程序开头插入Module java_base = Field.class.getModule(), unnamed = FieldHelper.class.getModule(); java_base.addOpens("java.lang.reflect", unnamed); java_base.addOpens("java.util", unnamed); 以编程方式消除警告。【参考方案3】:

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

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

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

【讨论】:

以上是关于获取jdk12中java.lang.reflect.Fields的声明字段的主要内容,如果未能解决你的问题,请参考以下文章

Caused by: java.lang.reflect.InvocationTargetExceptio

自己写一个java.lang.reflect.Proxy代理的实现

JAVA反射系列之Field,java.lang.reflect.Field使用获取方法

解决Scala项目中java.lang.reflect.InaccessibleObjectException报错

java.lang.reflect.Method.getAnnotation()方法示例通过反射获取到方法对象再获取方法对象上的注解信息

JAVA中反射机制六(java.lang.reflect包)