核心是确认JPMS触发并收紧可见性边界:检查module-info.java的exports/opens声明,识别unnamed module访问、用jdeps查依赖,升级适配库,禁用全局--add-opens。

遇到模块化环境下的非法访问错误,核心是确认问题是否由 JPMS(Java Platform Module System)触发,并同步收紧内部类与变量的可见性边界。这两件事本质一致:都是通过显式控制暴露面来提升健壮性与安全性。
快速定位未导出包访问错误
错误堆栈中若出现 module not exported、cannot access class ... because module X does not export Y 或涉及 java.lang.Module、jdk.internal.module.Modules,基本可判定为模块系统拦截所致。
- 检查报错类所在的模块是否在
module-info.java中exports了对应包;若该包仅用于反射(如 Jackson 反序列化、Spring 注入),还需opens而非仅exports - 观察发起访问的模块名——如果是
unnamed module,说明调用方是传统类路径下的 JAR,它默认无法访问模块路径中任何未导出的包 - 运行时加
--add-opens java.base/java.lang=ALL-UNNAMED(仅限调试)或--illegal-access=debug(Java 9–15)看警告源头,定位哪个模块在尝试越界访问
验证依赖链中的模块兼容性
第三方库若未提供 Automatic-Module-Name 或缺失 module-info.class,JVM 将其视为未命名模块,且对其启用强封装——即使你导出了包,它也访问不到。
- 用
jdeps --list-deps --module-path mods/ your-app.jar查看依赖图,识别哪些是unnamed module - 升级到已声明模块信息的库版本(如 Spring Boot 2.6+、Jackson 2.12+ 均支持 JPMS)
- 避免混合使用类路径和模块路径;如必须共存,对关键跨包调用补上
--add-exports启动参数(例如--add-exports java.base/sun.nio.ch=ALL-UNNAMED)
按最小权限原则优化变量封装
模块导出控制的是包级可见性,而变量封装控制的是类内成员粒度。两者叠加才能真正封住非法访问入口。
- 非 public 类中的字段,一律用
private修饰;仅当需被子类继承时考虑protected,且应有明确设计意图 - 避免
public字段,改用 getter/setter,并在 setter 中加入校验逻辑(如非空、范围限制) - 对常量使用
public static final,但敏感配置类(如数据库连接参数)应彻底私有化,通过工厂或注入方式提供受控实例
警惕 opens 指令的滥用风险
opens 是为反射开的后门,一旦开放整个包甚至模块,就等于绕过模块系统的安全屏障。
- 不要写
opens com.example.internal;,而应精确到具体包 + 明确授权目标模块,例如opens com.example.model to spring.core, com.fasterxml.jackson.databind; - 禁用全局反射开放(如
open module xxx { ... }),这会让任意模块都能反射读取内部状态 - 生产环境避免使用
--add-opens=ALL-UNNAMED,它会削弱模块隔离效果,增加攻击面

















