Appearance
📦 模块化与包管理(JPMS、Maven、Gradle 深入)
本章覆盖 Java 9 引入的模块系统(JPMS)的动机、核心概念、实践模式与迁移策略,并系统梳理 Maven/Gradle 的依赖管理、版本对齐与冲突消解方法,帮助你构建稳定、可维护的大中型 Java 项目。
1. 为什么需要模块化(JPMS)
- 明确的边界与封装:只导出需要被使用的 API 包,隐藏内部实现。
- 减少类路径地狱(Class-Path Hell):可声明依赖关系、检测循环依赖与缺失。
- 更好的启动与运行时图谱:支持
jlink定制最小运行时镜像。 - 安全与可维护:
opens/exports区分反射可见性,限制随意访问。
2. 模块基础:module-info.java
2.1 基本语法
java
module com.example.app {
requires java.base; // 隐式依赖,可省略
requires java.sql; // 声明依赖
exports com.example.app.api; // 导出 API 包供外部使用
exports com.example.app.util; // 可导出多个包
// 仅对特定模块开放反射(JPA/Hibernate 等常见)
opens com.example.app.entity to org.hibernate.orm.core;
// 服务提供与消费(SPI)
uses com.example.spi.PaymentService;
provides com.example.spi.PaymentService
with com.example.impl.WechatPaymentService;
}2.2 重要关键字
exports:编译期与运行期均对外可见(编译依赖 & 访问)。opens:仅在运行期对反射开放(不提供编译期可见性)。requires transitive:传递依赖,A→B(transitive),则依赖 A 的模块自动读取 B。requires static:仅编译期依赖,运行时可选(常用于可选功能)。
2.3 可读性与可访问性
- 模块图决定“可读性”(readability),
exports/opens决定“可访问性”(accessibility)。 - 通过
java --describe-module com.example.app查看模块信息。
3. 模块化常见结构与陷阱
3.1 自动模块与未命名模块
- 自动模块:将类路径上的 JAR 放到模块路径(--module-path),其名称来源于 JAR 文件名。
- 未命名模块:仍在类路径(-classpath)上的代码,视为未命名模块,能读取所有命名模块,但被强封装模块读取受限。
建议尽量迁移到“全模块路径”,减少自动模块的不可预测性。
3.2 Split Package(包拆分)问题
- JPMS 不允许同名包来自多个模块(与 OSGi 类似),须合并或重命名。
- 典型于多模块项目复用
com.example.common包时需小心。
3.3 反射框架兼容
- JPA/Hibernate、Jackson、Spring 在运行期需要反射访问非公开成员,需在
module-info.java使用opens或open module(对所有包开放反射,极不推荐)。
4. 模块工具链:编译、运行、打包
4.1 编译与运行
bash
# 编译(模块路径)
javac --module-path libs -d out $(find src -name "*.java")
# 运行
java --module-path out:libs --module com.example.app/com.example.app.Main4.2 jlink 定制运行时
bash
jlink \
--module-path $JAVA_HOME/jmods:out \
--add-modules com.example.app \
--output runtime-image \
--strip-debug --compress=2 --no-header-files --no-man-pages生成仅包含所需模块的精简运行时,显著降低镜像大小与启动时间。
4.3 jmod 与多模块布局
jmod可封装本地库与配置;多模块项目建议统一src/<module>/目录结构。
5. 与主流框架协作(Spring、Hibernate、Jackson)
5.1 Spring
- Spring 6+/Boot 3+ 基于 Jakarta EE 命名空间,模块兼容更好。
- 对反射访问 Bean 需要:
java
module com.example.app {
opens com.example.app.config to spring.core; // 仅对 Spring 反射开放
opens com.example.app.bean to spring.beans, spring.core;
}5.2 JPA/Hibernate
java
module com.example.app {
opens com.example.app.entity to org.hibernate.orm.core;
}5.3 Jackson
java
module com.example.app {
opens com.example.app.dto to com.fasterxml.jackson.databind;
}6. 迁移策略:从类路径到模块路径
- 先清理 Split Package,归并共享包到独立模块。
- 为每个模块补充
module-info.java,明确exports与requires。 - 检查反射需求,按需
opens到具体模块,避免open module。 - 逐步将依赖从类路径迁至模块路径,减少自动模块。
- 最后使用
jlink优化分发镜像与启动。
7. Maven 依赖管理(深入)
7.1 依赖范围与传递
compile(默认)、provided、runtime、test、system、import。- 传递依赖通过最近优先与最短路径原则解析。
7.2 版本对齐与 BOM(Bill of Materials)
xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.3.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>- 上述方式统一管理版本,子依赖无需显式 version,避免冲突。
7.3 依赖冲突与排除
xml
<dependencies>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<exclusions>
<exclusion>
<groupId>org.reactivestreams</groupId>
<artifactId>reactive-streams</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>7.4 可重复构建与锁定
- 使用 Maven Resolver、
maven-dependency-plugin生成树,结合私有仓库锁定版本。
7.5 多模块 Maven 与 JPMS 协同
- 每个 Maven 模块对标一个 Java 模块;使用
maven-compiler-plugin设置--release与--module-path。
xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<release>17</release>
<compilerArgs>
<arg>--module-path</arg>
<arg>${project.build.outputDirectory}</arg>
</compilerArgs>
</configuration>
</plugin>8. Gradle 依赖管理(深入)
8.1 配置与依赖范围
- 常见配置:
implementation、api、compileOnly、runtimeOnly、testImplementation。 api会将依赖暴露给下游模块(类似requires transitive),implementation不会。
8.2 版本对齐与平台(platform)
gradle
dependencies {
implementation platform('org.springframework.boot:spring-boot-dependencies:3.3.0')
implementation 'org.springframework.boot:spring-boot-starter-web'
}8.3 依赖解析策略与锁定
gradle
configurations.all {
resolutionStrategy {
failOnVersionConflict()
force 'com.fasterxml.jackson.core:jackson-databind:2.17.1'
}
}
dependencyLocking {
lockAllConfigurations()
}8.4 JPMS 与 Gradle
- 使用
modularity插件或在tasks.compileJava中添加--module-path;多模块工程可借助java-library插件区分api与implementation。
9. 打包与分发
9.1 可执行 JAR(fat/uber JAR)与模块化
spring-boot-maven-plugin/shadowJar打包 fat JAR 便捷,但与 JPMS 双亲委派与包结构可能冲突。- 模块化项目生产推荐:常规分层镜像 +
jlink精简 JRE,或使用容器镜像多阶段构建。
9.2 容器与镜像体积优化
- JDK 17 alpine 基础镜像 +
jlink输出的自定义 runtime,可显著减小镜像体积与冷启动时间。
10. 实战:从传统多模块到 JPMS 的最小改造
- 为
common、domain、app三个 Maven 模块添加module-info.java:
java
// common 模块
module com.example.common {
exports com.example.common.util;
}
// domain 模块
module com.example.domain {
requires transitive com.example.common; // 下游自动可读 common
exports com.example.domain.model;
opens com.example.domain.entity to org.hibernate.orm.core;
}
// app 模块
module com.example.app {
requires com.example.domain; // 通过 domain 间接获得 common
requires spring.context;
opens com.example.app.config to spring.core, spring.beans;
}- 修复 Split Package,将重复包合并到
common。 - 对反射需求最小化
opens范围到具体包与模块。 - 使用
mvn -DskipTests package验证,再切换--module-path运行。 - 最后引入
jlink生成运行时镜像供生产分发。
11. 常见问题与排查
java.lang.LayerInstantiationException:模块层加载冲突,检查模块路径与版本。java.lang.IllegalAccessError:未导出/未开放的包被访问或反射。- 服务加载失败(SPI):忘记在
module-info.java中uses/provides。 - Split Package:合并或重命名,避免同包多模块。
12. 最佳实践清单
- 内聚:一个 Maven 模块对应一个 Java 模块;公共包单独模块化。
- 最小暴露原则:
exports只导出 API 包;运行期反射用opens精准授权。 - 统一版本:Maven BOM 或 Gradle platform 对齐;锁定依赖避免漂移。
- 构建可复制:本地/CI 使用相同仓库与锁定策略;产物可追溯。
- 分发优化:结合
jlink与精简镜像,缩短冷启动、提升可观测性。
13. 参考配置模板
13.1 Maven(片段)
xml
<properties>
<maven.compiler.release>17</maven.compiler.release>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<release>${maven.compiler.release}</release>
</configuration>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version>
</plugin>
</plugins>
</pluginManagement>
</build>13.2 Gradle(片段)
gradle
plugins {
id 'java-library'
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
dependencies {
implementation platform('org.springframework.boot:spring-boot-dependencies:3.3.0')
api 'com.fasterxml.jackson.core:jackson-annotations'
implementation 'com.fasterxml.jackson.core:jackson-databind'
}掌握 JPMS 能让大型项目的边界更清晰、依赖更可控;熟练使用 Maven/Gradle 的版本平台与冲突策略,能显著降低“依赖地震”的风险。建议与“JVM 深入与性能调优”、“并发编程高级”等章节组合练习与实践。
