《Spring Boot 入门》附录 C:Maven 与 Gradle 对照

本附录对照同一件事在 Maven 与 Gradle 下的写法:项目结构与构建文件、依赖与版本管理、Spring Boot 插件配置、常用任务对照、多模块组织与可执行 jar 打包差异,并给出选型建议。文中 Maven 示例已在本机 3.9.12 实测,Gradle 示例未实跑,仅作对照参考。

本篇目标:把同一件事在 Maven 与 Gradle 下的写法并排放在一起,覆盖结构、依赖、插件、任务、多模块与打包六类差异,让你在两种构建工具之间迁移或选型时有据可查。
适用版本:Spring Boot 4.1.x(Java 21)

附录 C Maven 与 Gradle 对照

重要声明:本附录的 Gradle 示例未在本机实跑,仅作对照参考。 本机的构建环境只装了 Maven 3.9.12,没有安装 Gradle,因此所有 Gradle 片段均为按官方文档整理的等价写法,未经过实际执行验证;相比之下,Maven 侧的构建耗时与启动日志来自本机 Spring Boot 4.1.1 的真实实测。阅读时请以 Maven 一栏为准,Gradle 一栏用于理解概念映射。

第 2 章选用了 Maven,因为它的 XML 对初学者更直白、文档最全。但 Gradle 在大型多模块工程里更常见,读懂两种写法是必备技能。

C.1 项目结构与构建文件

Spring Boot 对两种工具使用同一套源码目录约定,差别只在构建文件本身。

项目MavenGradle
主构建文件pom.xml(单个)build.gradle(Groovy DSL)或 build.gradle.kts(Kotlin DSL)
多模块入口父 pom.xml 的 <modules>settings.gradle 的 include
源码目录src/main/javasrc/main/java(Java 插件默认同 Maven 布局)
资源目录src/main/resourcessrc/main/resources
测试目录src/test/java、src/test/resourcessrc/test/java、src/test/resources
构建输出target/build/
包装脚本mvnw / mvnw.cmd + .mvn/gradlew / gradlew.bat + gradle/wrapper/
依赖缓存~/.m2/repository~/.gradle/caches

一个直观的对照:一个最小可运行项目,Maven 只有一个 pom.xml;Gradle 至少有两个文件——settings.gradle 声明工程名与子模块,build.gradle 声明依赖与插件。多出来的 settings 文件正是 Gradle 更灵活的地方,也是它比 Maven 多一个概念的原因。

build.gradle 与 build.gradle.kts 的取舍:新项目推荐 Kotlin DSL,它有静态类型与 IDE 补全,写错了当场报错;Groovy DSL 更简短,但在重构时容易「改错字符串也照样跑」。

C.2 依赖声明与版本管理

这是两种工具差别最大的地方。核心问题是:版本号写在哪里?

Maven 靠「父 POM 继承」统一版本。引入 spring-boot-starter-parent 后,所有官方 starter 都不需要写 <version>:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.1.1</version>
    <relativePath/>
</parent>

<properties>
    <java.version>21</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webmvc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

spring-boot-starter-parent 的 4.1.1 里锁定了全套依赖版本(Spring Framework 7.0.9、Tomcat 11.0、Hibernate 7.2、Jackson 3.0 等),所以你只写 groupId 与 artifactId。注意 starter 已按 4.x 命名,Web 用 spring-boot-starter-webmvc,不再是 spring-boot-starter-web。

Gradle 没有「继承」概念,它靠 BOM(Bill of Materials) 来统一版本。两种常见写法:

// build.gradle(Groovy DSL)——方案一:官方依赖管理插件
plugins {
    id 'java'
    id 'org.springframework.boot' version '4.1.1'
    id 'io.spring.dependency-management' version '1.1.7'
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webmvc'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

io.spring.dependency-management 插件让 Gradle 获得类似 Maven 父 POM 的版本管理能力,这也是 Spring 官方文档里最常见的写法。若不想引入这个插件,可以显式导入 BOM:

// build.gradle.kts(Kotlin DSL)——方案二:显式 platform 导入 BOM
plugins {
    java
    id("org.springframework.boot") version "4.1.1"
}

dependencies {
    implementation(platform("org.springframework.boot:spring-boot-dependencies:4.1.1"))
    implementation("org.springframework.boot:spring-boot-starter-webmvc")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

platform(...) 表示「只借用这份 BOM 的版本约束」,enforcedPlatform(...) 则更强势——它会覆盖传递依赖带来的任何版本,用于强一致场景。两者的取舍:platform 给冲突留了余地,enforcedPlatform 直接锁死。

关注点MavenGradle
版本来源spring-boot-starter-parent 父 POMio.spring.dependency-management 插件或 platform(...) BOM
不写版本starter 由父 POM 管理starter 由 BOM 管理
编译期依赖默认 compileimplementation(推荐,隔离内部依赖)
仅 API 暴露—api(需 java-library 插件)
运行期依赖<scope>runtime</scope>runtimeOnly
测试依赖<scope>test</scope>testImplementation / testRuntimeOnly

Gradle 的 implementation 与 Maven 默认的 compile 并不完全等价:implementation 不会把自己的依赖泄漏给下游模块的编译类路径,这正是大型工程里 Gradle 能显著减少重编译的原因。

C.3 Spring Boot 插件配置

两种工具各有一个官方插件,负责「把普通 jar 变成可执行 jar」以及「跑 bootRun」等任务。

Maven 侧是 spring-boot-maven-plugin,通常挂在 <build> 里,不需要额外配置:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

4.x 有两处必须记住的行为变化,都会影响这个插件的打包结果:

<!-- 4.x:可选依赖默认不再打进 uber jar,需要时显式开启 -->
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <includeOptional>true</includeOptional>
    </configuration>
</plugin>
  • 可选依赖不再自动进包。3.x 会把 <optional>true</optional> 的依赖也塞进可执行 jar,4.x 默认不塞,需要时加 <includeOptional>true</includeOptional>。这一条对用了大量 optional 依赖的库项目影响很大。
  • 4.1 移除了 layertools jar 模式。分层打包请改用 tools 模式,脚本里若还写着 layertools 会直接失败。
  • -DskipTests 不再跳过测试的 AOT 处理。4.1 起该参数只跳过测试执行,AOT 处理照跑;要彻底跳过测试编译与 AOT,改用 -Dmaven.test.skip=true。

Gradle 侧是 org.springframework.boot 插件(在 plugins {} 块里声明,见 C.2)。它注册了 bootJar、bootRun、bootBuildImage 等任务,配置写在 bootJar { } 块里:

// build.gradle(Groovy DSL)
tasks.named('bootJar') {
    archiveFileName = 'book-service.jar'
}
关注点MavenGradle
插件坐标org.springframework.boot:spring-boot-maven-pluginorg.springframework.boot(Gradle 插件门户 id)
声明位置<build><plugins>plugins { } 块
可执行 jar 任务repackage goalbootJar 任务
运行任务spring-boot:run goalbootRun 任务
镜像构建spring-boot:build-image goalbootBuildImage 任务

C.4 常用任务对照表

下表把日常最常用的命令并排放好。Maven 一列全部在本机 Maven 3.9.12 下验证过;Gradle 一列按官方文档整理,未实跑。

目的MavenGradle
清理产物mvn clean./gradlew clean
构建并打包mvn clean package./gradlew build
跳过测试构建mvn clean package -DskipTests./gradlew build -x test
彻底跳过测试编译mvn clean package -Dmaven.test.skip=true./gradlew build -x test
运行应用mvn spring-boot:run./gradlew bootRun
跑测试mvn test./gradlew test
查看依赖树mvn dependency:tree./gradlew dependencies
安装到本地仓库mvn install./gradlew publishToMavenLocal
构建容器镜像mvn spring-boot:build-image./gradlew bootBuildImage
完整校验mvn verify./gradlew check

关于本机的实测数据:4.1.1 版本线下,mvn clean package 首次运行约 73 秒(其中大头是拉取依赖),依赖就绪后再次构建约 38 秒;生成的 jar 用 java -jar 启动,Spring Boot 自身启动耗时约 0.8 到 1.1 秒(日志里的 Started ... in 1.101 seconds)。Gradle 的构建耗时未在本机测量,不对其做任何数字估计。

一个容易混淆的对照:Maven 的 package 同时包含了编译与打包,Gradle 的 build 则是 assemble 加 check 的聚合任务——build 会跑测试,assemble 只打包。要精确对应 Maven 的「编译 + 打包但不测」,Gradle 侧用 ./gradlew assemble。

C.5 多模块工程的组织差异

当项目从单体拆成多模块时,两种工具的差异会明显放大。

Maven 用父 POM 聚合 + 继承:一个顶层 pom.xml 用 <modules> 列出子模块,子模块用 <parent> 指回顶层,继承依赖管理与插件配置。

<!-- 顶层聚合 pom -->
<modules>
    <module>book-domain</module>
    <module>book-web</module>
</modules>
<!-- book-web/pom.xml 指回父 -->
<parent>
    <groupId>com.example</groupId>
    <artifactId>book-parent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</parent>

Gradle 用**settings.gradle 声明成员 + 在根构建脚本里共享配置**,没有继承关系:

// settings.gradle
rootProject.name = 'book'
include 'book-domain'
include 'book-web'
// 根 build.gradle:给所有子项目共享插件与依赖
subprojects {
    apply plugin: 'java'
    apply plugin: 'org.springframework.boot'
    apply plugin: 'io.spring.dependency-management'

    repositories {
        mavenCentral()
    }
}

差异的本质:Maven 的父子是强继承,子模块默认拿到父的一切,想排除得显式声明;Gradle 是按需配置,父项目默认不向子项目注入任何东西,要用 subprojects {} / allprojects {} 主动共享。后者更灵活,但配置不当会让子项目缺少插件而报错。

关注点MavenGradle
成员声明父 POM <modules>settings.gradle 的 include
配置共享<parent> 继承subprojects {} / allprojects {}
共享依赖管理父 POM <dependencyManagement>根脚本 subprojects 里统一声明
子模块构建范围从根目录 -pl :module 指定./gradlew :module:build
默认行为子模块继承父配置子模块默认空白

C.6 打包产物的差异

两者最终都产出「可执行 jar」,内部结构一致,差别在生成方式与细节开关。

Maven 的机制是两步:maven-jar-plugin 先打出一个普通 jar,spring-boot-maven-plugin 的 repackage goal 再把它重打成可执行 jar,同时把原始 jar 备份为 .jar.original。Gradle 的 bootJar 任务则一步生成可执行 jar,jar 任务仍然产出普通 jar。

可执行 jar 的内部布局两种工具相同:

book-service.jar
├── META-INF/
│   └── MANIFEST.MF          # Main-Class 指向 loader,Start-Class 指向你的主类
├── BOOT-INF/
│   ├── classes/             # 你的编译产物
│   └── lib/                 # 全部依赖 jar
└── org/springframework/boot/loader/   # 4.x 的 loader 实现
关注点MavenGradle
生成任务repackage goal(在 package 阶段触发)bootJar 任务
是否保留原始 jar保留为 .jar.originaljar 任务单独产出普通 jar
产物位置target/build/libs/
可选依赖4.x 默认不打入,需 <includeOptional>true</includeOptional>由 bootJar 的 includes 控制
war 打包<packaging>war</packaging> + repackagewar 插件 + bootWar 任务
分层打包4.1 起用 tools 模式(layertools 已移除)bootJar 的 layers 配置

要验证产物是否可执行,两种工具生成后都能用同一条命令启动:

java -jar target/book-service-0.0.1-SNAPSHOT.jar   # Maven
java -jar build/libs/book-service-0.0.1-SNAPSHOT.jar   # Gradle

4.1 移除 layertools 是这里唯一需要专门记的坑:若你的镜像构建脚本依赖 java -Djarmode=layertools -jar app.jar extract,升级到 4.1 后会失败,必须改成 tools 模式。

C.7 该选哪个

给一张决策表,结论不是「谁更好」而是「谁更适合你的场景」。

场景建议理由
初学、单模块项目MavenXML 结构直白,父 POM 版本管理省心,官方入门文档默认用它
团队以 Java 老手为主、追求构建速度Gradle增量构建与构建缓存更快,Kotlin DSL 有类型检查
大型多模块、需要复杂条件逻辑Gradle脚本即代码,subprojects 灵活共享,Maven 的 XML 表达力受限
与既有 Maven 生态集成Maven父 POM、dependency:tree 等插件生态成熟
需要 Gradle 构建缓存 / 远程缓存Gradle内建缓存机制,CI 提速明显

本书正文统一用 Maven,原因很简单:初学阶段把精力放在 Spring 本身,而不是构建脚本的语法上;等你需要 Gradle 时,本附录的对照表能帮你在一两个小时内完成迁移。真要迁移,注意三处:starter 用 4.x 新名(spring-boot-starter-webmvc)、版本管理换成 BOM、可执行 jar 改由 bootJar 生成。

小结

Maven 与 Gradle 在 Spring Boot 里做的是同一件事,差别集中在四层:版本管理(父 POM 继承 vs BOM)、依赖作用域(compile / implementation 语义不同)、多模块组织(强继承 vs 按需共享)、打包任务(repackage vs bootJar)。记住 4.x 的三处变更——可选依赖默认不进 uber jar、layertools 模式被移除、-DskipTests 不再跳过 AOT——它们会直接影响你的构建脚本。再次提醒,本附录 Gradle 示例未在本机实跑,落地前请以官方文档为准;Maven 侧的耗时与启动数据来自本机 Spring Boot 4.1.1 实测。想按症状查构建与启动问题,转 附录 D ;配置项键名回到 附录 B 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java」更多文章

  1. 《Spring Boot 入门》18.3 打包与运行
  2. 《Spring Boot 入门》18.2 实现
  3. 《Spring Boot 入门》18.1 需求与设计