靠 exports 控制包可见性 + requires 精确声明依赖 + 避免 open module,就能实现二方库的强隔离;漏掉任一环节,反射、类加载或跨模块调用都可能绕过封装。

直接说结论:靠 exports 控制包可见性 + requires 精确声明依赖 + 避免 open module,就能实现二方库的强隔离。不是加了 module-info.java 就算模块化,漏掉任一环节,反射、类加载或跨模块调用都可能绕过封装。
module-info.java 中 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 滥用
二方库依赖 java.logging 或 com.example.core 时,requires 要严格按实际使用范围写。滥用 transitive 会导致下游模块意外获得不该有的可读性,破坏隔离边界。
-
requires java.logging;✅ 仅当前模块可用日志 -
requires transitive java.logging;❌ 强制下游也“可读”日志模块,扩大攻击面 - 若二方库只是桥接某个服务(如 SPI),应改用
uses com.example.spi.Service,而非requires实现模块 - 对 JDK 内部模块(如
jdk.unsupported)的依赖要彻底删除,它们在 JDK 17+ 已被移除
运行时类加载失败?先检查 --module-path 和自动模块混用
二方库打成模块化 JAR 后,若下游项目仍用 --class-path 启动,JVM 会把它降级为“自动模块”,此时 exports 失效,所有 public 类又变回全局可见——强隔离瞬间归零。
- 启动命令必须用
java --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.java 加 opens com.example.utils.config to spring.core 或更糟的 open module,等于主动撕开隔离墙。
- 优先改代码:把需反射的类移到
exports包下,或提供工厂方法替代反射实例化 - 仅对真正需要运行时深度操作的配置类开放,例如
opens com.example.utils.config to java.desktop(仅限 JavaFX 场景) -
opens不继承,opens com.example.utils.config不代表其子包自动开放 - JDK 16+ 默认禁用非法反射(
--illegal-access=deny),此时opens是唯一合规解法,但务必限定目标模块
强隔离真正的难点不在语法,而在组织习惯:每个 requires 都得有业务依据,每个 exports 都得经 API 评审,而构建脚本里一个 --class-path 就能让所有设计失效。模块系统不替你做决策,它只确保你写的每行声明,都会在运行时被严格执行。

















