Java 17 模块系统的实战指南

在 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.javarequires <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.dbmodule-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 应用!

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注