module-info.java的核心作用是精确控制包的导出范围以实现封装隔离;只导出API包,不导出impl/internal包;用opens谨慎开放反射权限;将敏感类移入未导出未开放的嵌套包;通过services机制替代硬依赖。

在 Java 9+ 的模块化系统中,module-info.java 的核心作用之一是**精确控制包的导出范围**,从而实现类级别的封装隔离。业务模块中的敏感辅助变量类(如含密钥、配置、内部状态的工具类)不应被其他模块直接访问——这不是靠命名约定或文档约束,而是靠模块系统的强制访问限制来保障。关键在于:不导出、不开放、不反射穿透。
只导出真正需要被依赖的 API 包
业务模块通常包含多个包:com.example.order.api(对外契约)、com.example.order.impl(实现)、com.example.order.internal(敏感辅助类所在)。只需导出明确设计为公共接口的包:
module com.example.order {
exports com.example.order.api;
// 不 exports com.example.order.impl
// 更不 exports com.example.order.internal
}
未导出的包对其他模块完全不可见——编译期报错,运行时也无法通过 ClassLoader 加载其公开类(除非使用非法反射,但模块系统可进一步拦截)。
用 opens 谨慎开放反射访问(仅当必须)
若某些框架(如 Spring、Jackson)需通过反射访问内部类的私有字段(如敏感配置类的 @Value 字段),不能直接导出整个包,而应仅对特定包 + 特定模块开放反射权限:
opens com.example.order.internal to spring.core;opens com.example.order.internal to com.fasterxml.jackson.databind;
这样既满足框架需求,又避免其他模块获得反射能力。注意:opens 不等于 exports,它只放开反射访问,不暴露类型可见性。
将敏感类移入未导出且未开放的嵌套包(推荐做法)
更彻底的方式是物理隔离:把敏感辅助变量类放入一个**完全未导出、未开放、甚至不声明在 module-info 中的子包**,例如:
com.example.order.internal.secrets.KeyHoldercom.example.order.internal.config.CryptoConfig
只要 com.example.order.internal 整个包未被 exports 或 opens,这些类就天然不可见。即使同模块内其他类引用它们,也仅限于本模块代码——这是模块边界最干净的体现。
配合 requires static 和服务机制替代硬依赖
如果其他模块确实需要“感知”某些敏感能力(如加解密服务),不要让它们直接 new 辅助类,而是定义服务接口并用 ServiceLoader:
- 在
com.example.order.api中定义EncryptionService接口 - 在
com.example.order.internal中提供实现类(不导出) - 在
module-info.java中声明:provides EncryptionService with com.example.order.internal.SecretEncryptionServiceImpl; - 调用方用
ServiceLoader.load(EncryptionService.class)获取实例,无需知道实现类名或包路径
这种方式把敏感实现彻底隐藏在模块内部,对外只暴露契约与服务发现机制。

















