Java 项目构建选型:Maven、Gradle 与模块化实践
学完这篇,你能在半小时内为自己的 Java 项目选对 Maven 或 Gradle,并把单模块工程拆成能编译、能复用、不踩循环依赖坑的多模块结构。
第一步:先分清你的项目属于哪一类
选型不是比谁先进,而是比谁匹配:
- 公司内部服务、团队 Java 版本统一、CI 流程固定 → Maven,声明式、报错好查、新人零学习成本。
- 单体仓库、模块多、要求增量构建和构建缓存(Android、Kotlin、大型微服务群)→ Gradle。
- 需要 JDK 9+ 的模块化(JPMS)或 jlink 裁剪运行时 → 两者都能做,但 Gradle 对 module path 的处理更省心。
注意:不要用「Gradle 比 Maven 快」当唯一理由。小项目两者差距在秒级,而 Gradle 的构建脚本是代码,出错时排查成本更高。
第二步:Maven 的最小可用多模块骨架
根 `pom.xml` 用 `pom` 打包,只做聚合和版本管理:
<packaging>pom</packaging>
<modules>
<module>common</module>
<module>service</module>
<module>web</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.demo</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
子模块里引用兄弟模块时不要写版本号,交给 `dependencyManagement` 统一。常用命令:
mvn -T 1C clean install # 并行构建(1 线程/核)
mvn -pl web -am package # 只构建 web 及其依赖的模块
mvn dependency:tree -Dincludes=com.demo
注意:子模块继承父 POM 靠 `<parent>` 的 `relativePath`,默认 `../pom.xml`。如果目录层级不是标准的,必须显式写对,否则会去本地仓库找父 POM 然后报「找不到」。
第三步:Gradle 的最小可用多模块骨架
`settings.gradle.kts` 声明模块:
rootProject.name = "demo"
include("common", "service", "web")
根 `build.gradle.kts` 用 `subprojects` 或 `allprojects` 统一插件和 Java 版本,子模块只声明自己的依赖:
plugins { java }
java { toolchain { languageVersion = JavaLanguageVersion.of(21) } }
dependencies {
implementation(project(":common")) // 不对外暴露
api(project(":service")) // 对外暴露
}
命令用 Wrapper,别用本机 gradle:
./gradlew :web:build --configuration-cache
./gradlew dependencies --configuration runtimeClasspath
版本统一建议用 Version Catalog(`gradle/libs.versions.toml`),比在根脚本里拼变量好维护。
注意:`implementation` 和 `api` 的区别直接决定依赖是否传递。选错会导致「本地能编译、CI 编译不过」——因为 CI 是干净构建,缺的传递依赖才暴露出来。
第四步:多模块拆分与 JPMS 落地
拆模块只守一条规则:依赖方向单向,不允许环。常见分层是 `common`(工具与 DTO)→ `service`(业务)→ `web`(控制器与启动类)。发现 A 依赖 B、B 又依赖 A 时,把共用部分下沉到 `common`,而不是互相引用。
要真正用 JPMS,在 `src/main/java` 根目录放 `module-info.java`:
module com.demo.common {
exports com.demo.common.api; // 只导出对外 API
requires transitive java.logging; // 传递依赖
opens com.demo.common.entity to com.fasterxml.jackson.databind; // 反射框架需要
}
Gradle 中 `java.modularity.inferModulePath` 默认开启,模块化项目会自动走 module path;Maven 用 `maven-compiler-plugin` 即可,模块描述符放在各模块源码根。
注意:Spring Boot 的可执行 fat jar 与 JPMS 不兼容——`module-info.java` 会和 Boot 的类加载器冲突。用 Boot 打包就别加模块描述符,把模块化留给内部库;真要做模块化运行时,考虑用 jlink 自己组装。
校验依赖是否合理:
jdeps --module-path build/libs --print-module-deps com.demo.web
第五步:构建速度与依赖治理
Maven 侧:`mvn -T 1C` 并行、`-o` 离线、避免 `install` 跑全量,CI 上用 `-pl ... -am` 只构建受影响模块。
Gradle 侧:开构建缓存(`org.gradle.caching=true`)、配置缓存(`--configuration-cache`)、`--build-cache` 复用他人产物;少用 `allprojects`,它会让配置阶段无法缓存。
两者都要做版本收敛:Maven 用 BOM(`<scope>import</scope>`)统一第三方版本,Gradle 用 `platform()` 引 BOM。别再靠 `exclusions` 一个个排除冲突。
小结
- 选型看项目形态:标准化团队选 Maven,重增量与缓存选 Gradle,别看热闹。
- 多模块靠父 POM 的 `dependencyManagement` 或 Gradle 的 Version Catalog 统一版本,子模块不写版本号。
- 模块拆分守单向依赖,环依赖只能靠下沉共用代码解决。
- Gradle 里分清 `implementation` 与 `api`,Maven 里善用 `-pl -am` 增量构建。
- 用 Spring Boot fat jar 时不要加 `module-info.java`,模块化留给内部库或 jlink 场景。
转载请注明出处,版权归原作者所有。
星耀SVIP
管理员
黑卡会员





