本文详解如何在 java 平台模块系统(jpms)下合理设计面向扩展的模块化应用,重点解析 exports 与 exports to 在服务提供者接口(spi)场景中的语义差异、可见性控制原理及实际选型依据。
本文详解如何在 java 平台模块系统(jpms)下合理设计面向扩展的模块化应用,重点解析 exports 与 exports to 在服务提供者接口(spi)场景中的语义差异、可见性控制原理及实际选型依据。
在构建可扩展的模块化 Java 应用时,采用 Service Provider Interface(SPI) 模式配合 java.util.ServiceLoader 是业界公认的解耦策略。其核心在于将契约(接口)与实现分离,并通过模块系统控制契约的可见范围。您提出的三模块结构——app.view(UI 抽象)、app.engine(核心能力抽象)和 app(组合主模块)——正是典型的 SPI 架构范式,设计本身是合理且符合 JPMS 原则的。
关键在于理解模块导出(exports)的真实作用域边界。当 app.view 模块声明:
// app.view/module-info.java
module app.view {
exports app.view;
}该声明仅表示“允许其他显式依赖本模块的模块访问其 public 类型”,而非“全局公开”。换言之,一个未在自身 module-info.java 中写 requires app.view 的模块,根本无法编译通过任何对 app.view.View 的引用——JVM 的模块解析器会在编译期和启动期严格校验依赖图。因此,“被无关模块意外访问”的担忧在 JPMS 下并不成立;模块可见性由 requires 显式驱动,而非 exports 单方面开放。
相比之下,若改用限定导出:
立即学习“Java免费学习笔记(深入)”;
// ❌ 不推荐用于开放 SPI 场景
module app.view {
exports app.view to app, app.engine; // 仅允许 app 和 app.engine 使用
}这将导致第三方扩展模块(如 com.example.myview)因未被列入 to 列表而无法实现 app.view.View 接口——即使它正确声明了 requires app.view,编译器也会报错:“package app.view is not visible”。这实质上将 SPI 退化为封闭实现,违背了“开放扩展、封闭修改”的开闭原则。
✅ 正确做法是:
- 对所有定义 SPI 的模块(app.view, app.engine),使用无限制 exports;
- 主应用模块 app 通过 requires 声明依赖,并通过 uses 声明服务消费点:
// app/module-info.java
module app {
requires app.view;
requires app.engine;
uses app.view.View; // 告知 JVM:本模块将通过 ServiceLoader 查找 View 实现
uses app.engine.Engine;
}- 第三方实现模块只需声明 requires app.view(或 app.engine),并提供 provides ... with ...:
// com.example.myengine/module-info.java
module com.example.myengine {
requires app.engine;
provides app.engine.Engine with com.example.myengine.MyEngineImpl;
}⚠️ 注意事项:
- 避免滥用 requires transitive:若 app.view 错误声明 requires transitive app.engine,则任何 requires app.view 的模块将隐式获得对 app.engine 的访问权,破坏封装边界;
- ServiceLoader.load() 调用必须发生在运行时模块路径(--module-path)包含所有相关模块的前提下,建议在 app 模块的 main 方法中统一加载,避免跨模块初始化时序问题;
- 若需更高阶控制(如版本兼容性、条件加载),可结合 ServiceLoader.loadInstalled() 或自研服务注册中心,但基础 SPI + exports 已满足绝大多数扩展场景。
总结而言,您的原始设计完全正确:exports 是开放 SPI 的必要且安全的选择。所谓“过度暴露”实为对 JPMS 模块隔离机制的误解;真正的模块安全不靠隐藏接口,而靠显式依赖声明 + 编译期强校验。坚持无限制导出接口、严格约束实现模块的 provides 声明,即可构建既松耦合又高可扩展的模块化系统。


















