Java 9起JPMS默认禁止反射访问强封装模块私有成员,JDK 17+调用setAccessible(true)会抛InaccessibleObjectException;应优先改代码使用官方API,必要时用--add-opens精确开放模块权限。

Java 9起,模块系统(JPMS)默认禁止反射访问强封装模块中的私有成员。所谓“暴力反射”——即调用 setAccessible(true) 强行突破访问限制——在 JDK 17+ 上会直接失败,并抛出 InaccessibleObjectException。这不是 bug,而是设计行为:模块系统主动拦截,防止代码依赖不稳定的内部实现。
明确哪些包被模块封锁了
异常信息里会清楚指出被拦截的路径,例如:
module java.base does not "opens java.lang.invoke" to unnamed module @xxxx说明 java.base 模块未向你的应用(未命名模块)开放 java.lang.invoke 包。常见被锁死的包包括:
-
java.lang(如Throwable.detailMessage、Class.classLoader) -
java.util(如HashMap.table、ArrayList.elementData) -
java.lang.invoke(如SerializedLambda.capturingClass) -
java.lang.reflect和sun.misc等内部包
优先改代码,而不是加参数
反射访问 JDK 内部字段/方法本质是技术债。应尽量替换为官方支持的替代方案:
使用tbot机器ID身份文件配合tsh CLI,通过Teleport访问控制SSH登录托管主机或执行远程命令。
- 用
Throwable.getMessage()替代反射读取detailMessage字段 - 用
Map.entrySet()或Collection.toArray()替代直接操作HashMap.table或ArrayList.elementData - 序列化场景改用
VarHandle或MethodHandles.Lookup(JDK 9+ 官方推荐) - 框架级反射(如 MyBatis-Plus、cglib)升级到兼容 JPMS 的版本(如 MyBatis-Plus ≥ 3.5.0、cglib ≥ 3.3.0 + Spring Boot ≥ 2.6)
必须用反射时,精确配置 --add-opens
若短期无法改造代码,需通过 JVM 参数显式开放模块权限。格式严格为:
--add-opens 源模块/包名=目标模块名常见写法示例:
-
--add-opens java.base/java.lang=ALL-UNNAMED(解决String.value类错误) -
--add-opens java.base/java.lang.invoke=ALL-UNNAMED(修复 Lambda 序列化问题) -
--add-opens java.base/java.util=ALL-UNNAMED(应对集合类内部字段访问)
注意:ALL-UNNAMED 代表传统 classpath 应用;若使用自定义模块,需替换成对应模块名;多个参数可重复添加,但不能合并。
调试与验证技巧
别靠猜,用工具定位真实反射点:
- 运行时加
-XX:+ShowHiddenFrames查看完整堆栈,确认哪一行触发了反射 - 用
jdeps --jdk-internals your-app.jar扫描对 JDK 内部 API 的非法引用 - 在测试阶段启用 Java Agent(如
reflection-tracker.jar)捕获所有setAccessible调用 - IDEA 中需在「Run Configuration → VM Options」里填写参数,不能只写在 Program Arguments

















