在 Java 9 引入模块系统后,Java 开发者面临了一个全新的构建和部署生态。随着 Java 17 成为长期支持版本,模块化的实践已不再是可选项,而是提升代码可维护性、可重用性与安全性的关键手段。本文将从模块化的基础概念入手,结合实际项目案例,带你快速上手 Java 17 模块系统,并解决常见的集成与部署难题。
1. 模块系统的核心概念
| 关键术语 | 说明 |
|---|---|
module-info.java |
每个模块的入口文件,声明依赖、导出包、服务提供/消费。 |
requires |
指定当前模块依赖的其它模块。 |
exports |
导出包,外部模块才能访问。 |
opens |
打开包用于反射访问。 |
uses / provides |
声明服务的消费与实现,支持 SPI。 |
1.1 模块 vs JAR
模块是 JAR 的逻辑包装,包含更丰富的元数据。一个模块可以包含多个 JAR,但 JAR 也可以单独存在,不必属于模块。
2. 创建第一个模块
mkdir -p src/com.example.app/module-info.java
mkdir -p src/com.example.app/main/java/com/example/app
module-info.java:
module com.example.app {
requires java.sql;
requires com.fasterxml.jackson.databind;
exports com.example.app;
}
App.java:
package com.example.app;
public class App {
public static void main(String[] args) {
System.out.println("Hello, Java Modules!");
}
}
编译:
javac -d out $(find src -name "*.java")
运行:
java -p out -m com.example.app/com.example.app.App
3. 与第三方库集成
3.1 自动发现模块化 JAR
如果第三方 JAR 是模块化的(含 module-info.class),直接引用即可。若是非模块化的 JAR,Java 9+ 会自动创建 unnamed module。但若你想显式声明依赖,需在 module-info.java 加 requires <module-name>;。
3.2 转化为模块
将非模块化 JAR 包装为模块:
jar --create --file mylib.jar --module-info module-info.java -C classes .
module-info.java 示例:
module com.mycompany.mylib {
exports com.mycompany.mylib;
}
4. 模块的访问控制
- 导出包:默认只能被声明在
requires中的模块访问。 - 打开包:通过
opens允许反射访问,常用于框架(如 Spring)扫描。 - 服务:使用
provides/uses声明 SPI,支持模块化的插件机制。
4.1 典型错误
java.lang.IllegalAccessError: class com.example.app.SomeClass (in unnamed module) cannot access class com.example.db.Connection (in module com.example.db) because module com.example.db does not export com.example.db
解决办法:在 com.example.db 的 module-info.java 添加 exports com.example.db;。
5. 多模块项目构建
采用 Maven 或 Gradle 配置多模块:
5.1 Maven 示例
pom.xml(父 POM):
<modules> <module>app</module> <module>db</module> </modules>
app/pom.xml:
<dependencies> <dependency> <groupId>com.example</groupId> <artifactId>db</artifactId> <version>1.0.0</version> </dependency> </dependencies>
在 Maven 打包时,确保 maven-compiler-plugin 配置 --module-path。
5.2 Gradle 示例
java {
modularity.inferModulePath.set(true)
}
dependencies {
implementation project(':db')
}
6. 部署与运行时
使用 jlink 创建自定义运行时映像:
jlink \
--module-path $JAVA_HOME/jmods:out \
--add-modules com.example.app \
--output myapp-image
运行:
./myapp-image/bin/java -m com.example.app/com.example.app.App
7. 常见陷阱与最佳实践
| 位置 | 建议 |
|---|---|
| 模块名 | 避免使用 java.* 前缀;遵循包名倒序。 |
| 导出 | 只导出真正需要对外暴露的包,最小化攻击面。 |
| 打开包 | 仅对需要反射的包使用 opens,不建议全局开放。 |
| 服务 | 明确声明 provides,并在 META-INF/services 配置文件中列出实现类。 |
| 多模块 | 采用 Gradle/Maven 的多模块构建,避免手工管理依赖。 |
8. 未来展望
Java 17 将继续强化模块系统,尤其是在安全性、性能与构建工具集成方面。随着生态完善,模块化将成为 Java 开发的标准实践。掌握模块系统不仅能提升代码质量,更能为团队协作和持续交付提供坚实基础。
祝你在 Java 17 模块化之路上一路顺风,构建更安全、更高效的 Java 应用!

发表回复