[Java] Java 17 FAQ

概述: Java 17

FAQ for Java 17

Q: 利用反射机制private 属性的 Field 设置为 true(field.setAccessible(true))时报: "java.lang.reflect.InaccessibleObjectException: Unable to make field private int java.io.StringReader.next accessible: module java.base does not "opens java.io" to unnamed module @411e7bd3",如何解决?

问题描述

  • 利用反射机制private 属性的 Field 设置为 true(field.setAccessible(true))时报:

"java.lang.reflect.InaccessibleObjectException: Unable to make field private int java.io.StringReader.next accessible: module java.base does not "opens java.io" to unnamed module @411e7bd3"

问题分析

在 Java 17 中,由于模块系统的限制,直接使用反射机制访问私有属性可能会导致 InaccessibleObjectException 异常。
这是因为 Java 9 引入的模块系统Project Jigsaw)限制了不同模块之间的非法反射访问

解决方案

方法1. 使用 --add-opens JVM 参数 (亲测有效)

可以通过在启动 Java 程序时添加 --add-opens 参数来允许特定模块的反射访问。例如,如果你需要访问 java.io 包中的私有字段,可以使用以下VM Option参数:

--add-opens java.base/java.io=ALL-UNNAMED

这个参数允许所有未命名模块访问 java.base/java.io 包中的非公共成员

方法2. 修改代码以使用 MethodHandles.privateLookupIn

  • 在 Java 9 及以上版本中,可以使用 MethodHandles.privateLookupIn 方法来获取私有字段的访问权限。

以下是一个示例:

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;

public class ReflectionExample {
    public static void main(String[] args) throws Exception {
        StringReader stringReader = new StringReader("example");

        // 获取私有字段的 VarHandle
        VarHandle nextField = MethodHandles.privateLookupIn(StringReader.class, MethodHandles.lookup())
                .findVarHandle(StringReader.class, "next", int.class);

        // 设置字段值
        nextField.set(stringReader, 10);

        System.out.println("Modified next field value: " + nextField.get(stringReader));
    }
}

方法3. 使用 Unsafe 类 (亲测有效)

  • 虽然不推荐,但可以使用 Unsafe 类来绕过反射限制。

以下是一个示例:

import sun.misc.Unsafe;

public class UnsafeExample {
    public static void main(String[] args) throws Exception {
        StringReader stringReader = new StringReader("example");

        // 获取 Unsafe 实例
        Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);

        // 获取私有字段的偏移量
        Field nextField = StringReader.class.getDeclaredField("next");
        long offset = unsafe.objectFieldOffset(nextField);

        // 设置字段值
        unsafe.putInt(stringReader, offset, 10);

        System.out.println("Modified next field value: " + unsafe.getInt(stringReader, offset));
    }
}
  • 案例实践
import sun.misc.Unsafe;
import java.io.StringReader;
import java.lang.reflect.Field;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class SourceStringReader extends StringReader {
    ...
	
    /**
     * 获取 StringReader 的 next(游标位置/指针)
     * @description 利用反射原理,将 java.io.StringReader 的 private 属性 next 读取出来
     * @return
     */
    @SneakyThrows
    public int next(){
        int next = Integer.MIN_VALUE; //读取失败时,以此值为标志
        //反射方法1 : Java 17 中需结合 VM Option 参数 : `--add-opens java.base/java.io=ALL-UNNAMED`
        //java.lang.reflect.Field field = java.io.StringReader.class.getDeclaredField("next");
        //field.setAccessible(true);
        //next = field.getInt( this );//读取 next 的值
        ////field.set(this, Integer.MIN_VALUE);//设置字段的值

        //反射方法2: 基于 Unsafe
        Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        // 获取私有字段的偏移量
        Field nextField = StringReader.class.getDeclaredField("next");
        long offset = unsafe.objectFieldOffset(nextField);
        next = unsafe.getInt(this, offset);
        //unsafe.putInt(stringReader, offset, 10);// 设置字段值

        return next;
    }
	
	...
}

总结

  • 推荐使用 --add-opens 参数来解决反射访问私有字段的问题,因为它是最简单且符合 Java 模块系统规范的解决方案。
  • 如果需要更复杂的反射操作,可以考虑使用 MethodHandles.privateLookupInUnsafe 类,但需要注意这些方法可能带来的安全性和稳定性问题。

X 参考文献

posted @ 2025-03-14 09:21  千千寰宇  阅读(164)  评论(0)    收藏  举报