在 Java 9 之后,JVM 引入了模块系统(Project Jigsaw),它为大型企业项目提供了一套更为严格的模块化机制。本文从模块系统的基本概念、核心文件到企业应用的实战经验进行系统阐述,并给出一个完整的示例演示如何将现有项目拆分为可复用的模块。
一、模块化的目标
- 强类型编译检查 – 在编译阶段就能确定依赖关系,避免运行时缺失类导致的
NoClassDefFoundError。 - 封装与信息隐藏 – 通过
exports语句声明对外公开的包,未声明的包默认对外不可见。 - 可组合性 – 模块化的单元可以被其他模块重用,促进代码复用。
- 更快的启动与更小的占用 – JDK 在
jlink生成自定义运行时镜像时,只会包含使用到的模块。
二、核心文件:module-info.java
module com.mycompany.payments {
requires java.base; // 隐式依赖,常写可省略
requires com.mycompany.common; // 需要依赖的其他模块
requires java.sql; // 第三方模块
exports com.mycompany.payments.api; // 对外公开的包
exports com.mycompany.payments.impl to com.mycompany.web; // 仅对 com.mycompany.web 模块可见
opens com.mycompany.payments.internal to com.mycompany.web; // 运行时反射可见
uses com.mycompany.common.PaymentProcessor; // 通过 ServiceLoader 读取实现
}
- requires:声明对其他模块的依赖,若依赖未被解析编译器会报错。
- exports:公开包给所有模块;可针对特定模块限定访问。
- opens:开放包给运行时反射;可限定目标模块。
- uses / provides:实现 Service Provider 机制,模块间通过
ServiceLoader进行解耦。
三、从传统项目到模块化的迁移步骤
- 分析项目结构 – 将功能域拆分为若干独立包。
- 创建 module-info.java – 为每个子项目生成模块描述文件。
- 调整依赖 – 用
requires替代传统的 classpath 依赖,保证所有引用都能在模块中解析。 - 封装内部实现 – 对不需要外部访问的包使用
opens或不声明exports。 - 改用 ServiceLoader – 若项目使用插件或策略模式,改写为模块化的
uses/provides。 - 构建自定义运行时 – 利用
jlink打包只包含所需模块的运行时镜像,减小体积。
四、企业级项目中的常见挑战
| 挑战 | 解决方案 |
|---|---|
| 旧第三方库不支持模块 | 1) 通过 --add-modules 临时引入 2) 使用 --patch-module 将 jar 作为模块修补 3) 在 Maven/Gradle 中配置 module-path |
| 多模块之间的循环依赖 | 1) 通过拆分共通功能为 common 模块 2) 引入 exports 只对特定模块开放 3) 对不需要暴露的内部类使用 opens |
| IDE 和构建工具兼容性 | 1) Eclipse/IntelliJ IDEA 2023+ 原生支持 2) Maven:使用 maven-compiler-plugin 配置 --module-path 3) Gradle:java { modularity.inferModulePath.set(true) } |
| 性能调优 | 1) 使用 jlink 打包可执行 JAR 2) 开启 -XX:+UseCompressedOops 3) 对频繁加载的模块进行预编译 |
五、完整示例:从单体到模块化
1. 项目结构
/payment-app
├─ api // 业务接口
├─ impl // 业务实现
├─ common // 公共工具
├─ web // 前端交互
└─ pom.xml
2. 每个模块的 module-info.java
common
module com.mycompany.common {
exports com.mycompany.common.util;
}
api
module com.mycompany.payments.api {
requires com.mycompany.common;
exports com.mycompany.payments.api;
}
impl
module com.mycompany.payments.impl {
requires com.mycompany.payments.api;
provides com.mycompany.payments.api.PaymentProcessor
with com.mycompany.payments.impl.DefaultProcessor;
}
web
module com.mycompany.web {
requires com.mycompany.payments.api;
uses com.mycompany.payments.api.PaymentProcessor;
}
3. Maven 配置(示例)
<properties> <maven.compiler.release>17</maven.compiler.release> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>17</source> <target>17</target> <compilerArgs> <arg>--module-path</arg> <arg>${project.build.directory}/libs/*</arg> </compilerArgs> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals><goal>shade</goal></goals> </execution> </executions> </plugin> </plugins> </build>
4. 构建自定义运行时镜像
jlink --module-path target/modules \
--add-modules com.mycompany.web \
--output runtime
运行:
./runtime/bin/java -m com.mycompany.web/com.mycompany.web.Main
六、结语
Java 9 之后的模块系统为企业级应用提供了更高的安全性、可维护性和可部署性。虽然迁移过程中会遇到依赖、工具和性能等挑战,但只要遵循模块化原则、合理拆分功能模块,并结合 jlink 等工具,最终将获得更小、更快、更易维护的 Java 应用。
小贴士:在开始迁移之前,可以先在一个独立的 Git 分支或虚拟机中完成模块化实验,确保不影响现有生产环境。

发表回复