Java模块化(JPMS)是支撑大规模代码重用的基础设施,package不等于module:前者无访问控制、无依赖验证、无法封装资源;module-info.java通过requires/exports/opens显式声明依赖与可见性,exports限制public访问,opens仅允许反射访问;混用-classpath会创建隐式命名模块破坏隔离。

Java 代码重用与模块化不是两个并列概念,而是演进关系:重用是目标,模块化(尤其是 module-info.java 引入的 JPMS)是支撑大规模重用的基础设施。没有模块边界约束的“重用”,往往退化为复制粘贴或脆弱的包级耦合。
为什么 package 不等于 module
很多人把 package 当作模块用,比如建个 com.example.utils 包放一堆静态工具类。这能实现基础复用,但存在明显缺陷:
-
package无访问控制粒度——无法声明“只允许 A 模块调用本模块的某个类”,public类对整个类路径可见 - 运行时无依赖验证——即使你删了
commons-lang3的 jar,只要没调用其类,JVM 仍能启动,错误延迟暴露 - 无法真正封装资源——
resources/下的配置文件、SPI 实现类默认全部对外可读可发现
而 module-info.java 显式声明 requires、exports、opens,让 JVM 在启动阶段就校验依赖完整性,并限制包的可访问范围。
exports 和 opens 的关键区别
两者都用于开放包,但语义和用途完全不同:
全诚易惠通优惠折扣信息店铺管理系统是全诚团队继 “全诚商城”“外卖通” 之后又一新概念重量级作品,该系统以收集本地所有店铺优惠折扣信息为核心,在构思、设计、代码处理上都做了严密的部署和检查,继承了全诚系列产品核心模块的基础上进化而来,即为新作品,也系高度成熟度的作品,加之全诚团队精心技术支持,可为用户营造一个长期可靠的系统运行环境。本系统较易惠通相比,业务和经营范围覆盖面积更广更大,可涵盖本地所有
立即学习“Java免费学习笔记(深入)”;
-
exports com.example.api;:仅允许其他模块访问该包下的public类型及其public成员(编译期 + 运行期强约束) -
opens com.example.config;:允许其他模块在运行时通过反射访问该包下所有类型(包括private字段/方法),专为序列化、测试、框架注入等场景设计 - 若只需导出 API,绝不要用
opens替代exports,否则破坏封装性
module my.app {
requires java.base;
requires com.fasterxml.jackson.databind;
exports com.myapp.api; // ✅ 正常提供接口
opens com.myapp.config; // ✅ 允许 Jackson 反射读写 private 字段
}
模块路径(--module-path)下 classpath 的残留风险
启用模块系统后,仍混用 -cp(即 classpath)会触发隐式命名模块(unnamed module),它能读取所有其他模块的 exports,也能被所有模块读取——相当于把模块墙凿了个大洞。
- 现象:
javac --module-path mods -d out Main.java编译成功,但运行时抛NoClassDefFoundError,因为某依赖实际来自 classpath 而非模块路径 - 验证方式:启动时加
--show-module-resolution,观察每个类加载自哪个模块 - 修复原则:所有依赖(包括自己编译的模块)必须统一走
--module-path;如需兼容传统 jar,用jar --file=xxx.jar --describe-module检查是否含Automatic-Module-Name
模块化真正的难点不在写 module-info.java,而在于重构已有项目时识别隐式依赖——比如一个 Utils.class 静态方法内部调用了 javax.xml.bind,而这个包在 Java 9+ 已移出 java.base,不显式 requires 就会在运行时报错。这类问题不会在编译时报出,必须靠模块化启动验证才能暴露。









