如何利用 JDK 9 的模块化系统(Jigsaw)实现核心二方库的代码可见性强隔离
通过exports精确控制包可见性、requires最小化依赖声明、避免openmodule,可有效实现二方库代码强隔离。反射访问需严格限定opens目标模块,自动模块会破坏封装,启动必须使用--module-path而非--class-path,确保每行声明在运行时被严格执行。
一句话总结:靠 exports 控制包可见性 + requires 精确声明依赖 + 避免 open module,就能实现二方库的强隔离;漏掉任一环节,反射、类加载或跨模块调用都可能绕过封装。

上面那个框就是核心原则。但现实是,很多人以为写了 module-info.ja va 就算模块化了,结果运行时问题百出——反射、类加载、跨模块调用,哪个都能把封装撕个口子。关键在于,每一行声明都得经得起推敲。
module-info.ja va 中 exports 必须精确到包,不能省略或写错
二方库(比如内部通用工具包 com.example.utils)想只暴露 API,就必须显式导出接口包,同时禁止导出实现包。常见错误是:导出整个模块名、写错包路径、或者误用通配符——JPMS 根本不认 exports com.example.* 这种写法。
exports com.example.utils.api;✅ 只让外部看到接口定义exports com.example.utils;❌ 把 impl、internal 全暴露了exports com.example.utils.internal;❌ 内部包不应对外可见- 未声明
exports的任何包,默认不可被其他模块访问,即使类是public
requires 声明必须最小化,避免 transitive 滥用
二方库依赖 ja va.logging 或 com.example.core 时,requires 要严格按实际使用范围写。滥用 transitive 会带来连锁反应——下游模块意外获得不该有的可读性,隔离边界就形同虚设。
requires ja va.logging;✅ 仅当前模块可用日志requires transitive ja va.logging;❌ 强制下游也“可读”日志模块,扩大攻击面- 若二方库只是桥接某个服务(如 SPI),应改用
uses com.example.spi.Service,而非requires实现模块 - 对 JDK 内部模块(如
jdk.unsupported)的依赖要彻底删除,它们在 JDK 17+ 已被移除
运行时类加载失败?先检查 --module-path 和自动模块混用
二方库打成模块化 JAR 后,若下游项目仍用 --class-path 启动,JVM 会把它降级为“自动模块”,此时 exports 直接失效,所有 public 类又变回全局可见——强隔离瞬间归零。这个坑最常见,也最容易忽略。
- 启动命令必须用
ja va --module-path lib/ --module myapp/com.example.Main,不能夹带-cp - 检查第三方依赖是否为自动模块:运行
jdeps --module-path lib/ --summary myapp.jar,输出中含unnamed module或automatic module即为风险点 - 自动模块的名称默认来自 JAR 文件名(如
utils-1.2.0.jar→utils),易与真实模块名冲突,建议统一重命名并补全Automatic-Module-NameMANIFEST 属性
反射访问被拒?别急着加 opens,先确认是否真需要
Spring、Hibernate 这些框架常因为反射失败报 IllegalAccessError。但很多人第一反应就是往 module-info.ja va 里加 opens com.example.utils.config to spring.core,甚至直接写成 open module——这等于主动拆了隔离墙。
- 优先改代码:把需要反射的类移到
exports包下,或者提供工厂方法代替反射实例化 - 仅对真正需要运行时深度操作的配置类开放,例如
opens com.example.utils.config to ja va.desktop(仅限 Ja vaFX 场景) opens不继承,opens com.example.utils.config不代表其子包自动开放- JDK 16+ 默认禁用非法反射(
--illegal-access=deny),此时opens是唯一合规解法,但务必限定目标模块
强隔离真正的难点不在语法,而在组织习惯:每个 requires 都得有业务依据,每个 exports 都得经 API 评审,而构建脚本里一个 --class-path 就能让所有设计失效。模块系统不替你做决策,它只确保你写的每行声明,都会在运行时被严格执行。


































