构建工具 Maven / Gradle
欢迎来到第十二阶段——工程化与生态。前面十一章我们写的代码都是”单文件”——一个 Main.java,javac 编译,java 运行。但真实项目不是这样的:几十个模块、上百个依赖、自动化测试、打包发布——靠 javac 手敲根本不行。这一阶段我们看怎么把”代码”变成”工程”。第一步就是构建工具——Maven 和 Gradle。
构建工具的核心职责:
- 依赖管理 —— 自动从远程仓库下载 jar 包及它的依赖。
- 构建生命周期 —— compile → test → package → install → deploy 一键搞定。
- 项目结构标准化 —— 约定优于配置,目录结构大家都一样。
- 多模块支持 —— 大项目拆成多个模块统一构建。
一、Maven
Maven(依地语”知识的积累者”)是 Apache 2002 年开源的构建工具,至今仍是 Java 业界事实标准。它的核心思想是约定优于配置(Convention over Configuration)——你不用告诉它源码在哪、编译输出在哪,按它的约定放就行。
1.1 标准目录结构
my-app/
├── pom.xml # 项目对象模型 (Project Object Model)
├── src/
│ ├── main/
│ │ ├── java/ # 主代码
│ │ └── resources/ # 资源文件 (配置、properties)
│ └── test/
│ ├── java/ # 测试代码
│ └── resources/ # 测试资源
└── target/ # 编译输出 (Maven 自动生成)
├── classes/
└── my-app-1.0.jar
只要按这个结构放代码,Maven 就知道怎么编译、测试、打包。不用写一行配置。
1.2 POM 与坐标
pom.xml 是 Maven 的核心——描述项目信息、依赖、构建配置。每个项目有个”坐标”(Coordinate)唯一标识:
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<!-- 坐标三要素: groupId / artifactId / version -->
<groupId>com.example</groupId> <!-- 组织/公司 (反向域名) -->
<artifactId>my-app</artifactId> <!-- 项目名 -->
<version>1.0.0</version> <!-- 版本号 -->
<packaging>jar</packaging> <!-- 打包类型: jar/war/pom -->
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
</project>
坐标三要素 groupId:artifactId:version 唯一确定一个 jar——比如 com.google.code.gson:gson:2.10.1。Maven 仓库就是按这个坐标组织目录的。
1.3 依赖范围 Scope
<scope> 决定依赖在什么阶段可用:
| Scope | 编译 | 测试 | 运行 | 打包 | 例子 |
|---|---|---|---|---|---|
compile(默认) | ✅ | ✅ | ✅ | ✅ | Spring、Gson |
test | ❌ | ✅ | ❌ | ❌ | JUnit、Mockito |
provided | ✅ | ✅ | ❌ | ❌ | Servlet API(容器提供) |
runtime | ❌ | ✅ | ✅ | ✅ | JDBC 驱动 |
system | ✅ | ✅ | ❌ | ❌ | 本地 jar(不推荐) |
import | - | - | - | - | 引入 BOM |
<!-- JDBC 驱动: 运行时才需要, 编译时用不到 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<!-- Servlet API: Tomcat 提供, 打包不要带 -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
1.4 传递依赖与冲突
Maven 自动处理传递依赖——你依赖 A,A 依赖 B,Maven 自动把 B 也加进来。但这会引发版本冲突:
项目 -> A (依赖 B:1.0)
项目 -> C (依赖 B:2.0)
B 该用 1.0 还是 2.0?Maven 的策略是最短路径优先——直接依赖 > 传递依赖。同长度时先声明的优先。可以用 <exclusions> 排除某个传递依赖:
<dependency>
<groupId>com.example</groupId>
<artifactId>A</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>com.example</groupId>
<artifactId>B</artifactId> <!-- 排除 A 引入的 B -->
</exclusion>
</exclusions>
</dependency>
排查冲突用命令 mvn dependency:tree —— 打印完整依赖树,一目了然。
1.5 生命周期与命令
Maven 有三套独立生命周期:
- clean ——
pre-clean→clean→post-clean(清理 target) - default ——
validate→compile→test→package→verify→install→deploy - site —— 生成项目文档站点
每个阶段(phase)绑定零到多个插件目标(goal)。常用命令:
mvn clean # 清理 target
mvn compile # 编译主代码
mvn test # 运行测试
mvn package # 打包 (jar/war)
mvn install # 打包并装到本地仓库 ~/.m2/repository
mvn deploy # 发布到远程仓库
mvn clean package # 清理 + 打包 (常用组合)
mvn dependency:tree # 查看依赖树
mvn -DskipTests package # 打包但跳过测试
mvn install 后,其他项目就能用 com.example:my-app:1.0.0 依赖你。mvn deploy 是发布到 Nexus/Artifactory 等私服。
1.6 仓库与镜像
Maven 仓库分三层:
- 本地仓库
~/.m2/repository/—— 第一次下载后缓存。 - 私服(Nexus/Artifactory)—— 公司内部共享。
- 中央仓库
https://repo.maven.apache.org/maven2/—— 全球公共。
查找顺序:本地 → 私服 → 中央。
国内访问中央仓库慢,配镜像(~/.m2/settings.xml)走阿里云:
<settings>
<mirrors>
<mirror>
<id>aliyun</id>
<mirrorOf>central</mirrorOf>
<name>Aliyun Maven</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
</mirrors>
</settings>
1.7 多模块项目
大项目拆成多个模块,父 POM 统一管理:
my-project/
├── pom.xml # 父 POM (packaging=pom)
├── my-domain/ # 领域模型
│ └── pom.xml
├── my-service/ # 业务逻辑
│ └── pom.xml
├── my-web/ # Web 接口
│ └── pom.xml
└── my-app/ # 启动模块
└── pom.xml
父 POM:
<project>
<groupId>com.example</groupId>
<artifactId>my-project</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>my-domain</module>
<module>my-service</module>
<module>my-web</module>
<module>my-app</module>
</modules>
<!-- 统一管理依赖版本 (不引入, 只声明) -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
子模块继承父 POM,省去重复配置:
<project>
<parent>
<groupId>com.example</groupId>
<artifactId>my-project</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>my-service</artifactId>
<dependencies>
<!-- 引入其他模块 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>my-domain</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Boot 依赖不用写版本 (父 POM 管了) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
dependencyManagement 只声明版本,不实际引入——子模块用 <dependency> 时省略 <version> 即可。这是统一版本号的关键机制。
二、Gradle
Gradle 是 2012 年开源的新一代构建工具,结合了 Maven 的依赖管理和 Ant 的灵活性,用 Groovy/Kotlin DSL 代替 XML——更简洁、能编程。
2.1 build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.0'
id 'io.spring.dependency-management' version '1.1.4'
}
group = 'com.example'
version = '1.0.0'
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
repositories {
mavenCentral() // 中央仓库
maven { url 'https://maven.aliyun.com/repository/public' }
}
dependencies {
// scope 用关键字声明, 比 Maven 的 <scope> 简洁
implementation 'com.google.code.gson:gson:2.10.1' // 编译+运行
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' // 仅测试
runtimeOnly 'mysql:mysql-connector-java:8.0.33' // 仅运行
compileOnly 'jakarta.servlet:jakarta.servlet-api:6.0.0' // 仅编译
// Spring Boot 依赖管理下不用写版本
implementation 'org.springframework.boot:spring-boot-starter-web'
}
test {
useJUnitPlatform()
}
Gradle 的依赖 scope 关键字:
implementation—— 编译 + 运行(类似 Maven 的compile,但不暴露给上游)。api—— 编译 + 运行 + 暴露给上游(用于库项目)。compileOnly—— 仅编译(类似provided)。runtimeOnly—— 仅运行(类似runtime)。testImplementation—— 仅测试编译运行(test)。
2.2 任务与生命周期
Gradle 的核心是任务(Task):
tasks.register('hello') {
doLast {
println 'Hello from Gradle!'
}
}
tasks.register('count') {
doLast {
4.times { print "$it " }
}
}
gradle hello # 执行 hello 任务
gradle build # 构建 (compile + test + jar)
gradle clean # 清理
gradle test # 测试
gradle bootJar # Spring Boot 打包
gradle dependencies # 查看依赖树
Gradle 默认任务比 Maven 更灵活——能写代码逻辑,比如根据环境动态改依赖。
2.3 优点
- DSL 简洁 —— 同样配置比 XML 短一半。
- 构建速度快 —— 增量编译、构建缓存、守护进程,大型项目比 Maven 快 2~10 倍。
- 灵活 —— Groovy/Kotlin 能写任意逻辑,比 Maven 的插件机制自由。
- Android 默认 —— Android Studio 项目全是 Gradle。
三、Maven vs Gradle 选型
| 对比项 | Maven | Gradle |
|---|---|---|
| 配置文件 | pom.xml(XML) | build.gradle(Groovy/Kotlin DSL) |
| 学习曲线 | 平缓,约定严格 | 略陡,灵活 |
| 构建速度 | 中等 | 快(增量、缓存) |
| 灵活性 | 插件机制,较死板 | 任意编程逻辑 |
| 生态 | 最丰富 | 丰富(Android 主流) |
| 多模块 | 支持但繁琐 | 优雅 |
| 适合 | Java 后端、传统项目 | Android、新项目、大型项目 |
选型建议:
- 公司已有 Maven 体系 → Maven(统一最重要)。
- Spring Boot 项目 → 两者都行,看团队习惯。
- Android 项目 → Gradle(强制)。
- 大型多模块新项目 → Gradle(构建速度快)。
四、实战:依赖关系模拟
由于构建工具的代码不能在 Piston 运行(要装 Maven/Gradle),下面用 Java SE 模拟一个依赖解析器——演示 Maven 传递依赖与冲突解决的核心逻辑。
观察重点:
spring-core出现两个版本 5.3.0 和 5.3.1——Maven 选路径短的(这里都是深度 1,先声明的 5.3.0 胜出)。- 传递依赖自动加入——
spring-web引入了spring-core,不用你显式声明。scope=test的依赖不进主项目——junit不会被打到最终 jar 里。- 坐标唯一标识 jar——
groupId:artifactId:version三要素缺一不可。
五、本章小结
| 概念 | 核心要点 |
|---|---|
| 构建工具职责 | 依赖管理、生命周期、标准化、多模块 |
| Maven 坐标 | groupId:artifactId:version |
| Maven Scope | compile/test/provided/runtime |
| 传递依赖 | 自动引入依赖的依赖 |
| 冲突解决 | 最短路径优先,同深度先声明优先 |
| Maven 生命周期 | clean / default / site 三套 |
| Maven 仓库 | 本地 → 私服 → 中央 |
| Gradle DSL | Groovy/Kotlin,简洁灵活 |
| Gradle scope | implementation/api/compileOnly/runtimeOnly |
记忆口诀:
- 坐标三要素——groupId、artifactId、version。
- scope 四档——compile(默认)、test(测试)、provided(容器提供)、runtime(运行时)。
- 冲突最短路径优先——同深度先声明优先。
- Maven 三套生命周期——clean、default、site 互不影响。
- Gradle 比 Maven 快——增量编译 + 缓存。
- Android 强制 Gradle——其他看团队。
结语:从代码到工程
这一章我们看了 Maven 和 Gradle——把”一堆 .java 文件”组织成”可构建、可依赖、可发布的工程”。下一步——Git 版本控制,让多人协作不再互相覆盖。