构建工具 Maven / Gradle

欢迎来到第十二阶段——工程化与生态。前面十一章我们写的代码都是”单文件”——一个 Main.javajavac 编译,java 运行。但真实项目不是这样的:几十个模块、上百个依赖、自动化测试、打包发布——靠 javac 手敲根本不行。这一阶段我们看怎么把”代码”变成”工程”。第一步就是构建工具——Maven 和 Gradle。

构建工具的核心职责:

  1. 依赖管理 —— 自动从远程仓库下载 jar 包及它的依赖。
  2. 构建生命周期 —— compile → test → package → install → deploy 一键搞定。
  3. 项目结构标准化 —— 约定优于配置,目录结构大家都一样。
  4. 多模块支持 —— 大项目拆成多个模块统一构建。

一、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
testJUnit、Mockito
providedServlet API(容器提供)
runtimeJDBC 驱动
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-cleancleanpost-clean(清理 target)
  • default —— validatecompiletestpackageverifyinstalldeploy
  • 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 仓库分三层:

  1. 本地仓库 ~/.m2/repository/ —— 第一次下载后缓存。
  2. 私服(Nexus/Artifactory)—— 公司内部共享。
  3. 中央仓库 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 选型

对比项MavenGradle
配置文件pom.xml(XML)build.gradle(Groovy/Kotlin DSL)
学习曲线平缓,约定严格略陡,灵活
构建速度中等(增量、缓存)
灵活性插件机制,较死板任意编程逻辑
生态最丰富丰富(Android 主流)
多模块支持但繁琐优雅
适合Java 后端、传统项目Android、新项目、大型项目

选型建议

  • 公司已有 Maven 体系 → Maven(统一最重要)。
  • Spring Boot 项目 → 两者都行,看团队习惯。
  • Android 项目 → Gradle(强制)。
  • 大型多模块新项目 → Gradle(构建速度快)。

四、实战:依赖关系模拟

由于构建工具的代码不能在 Piston 运行(要装 Maven/Gradle),下面用 Java SE 模拟一个依赖解析器——演示 Maven 传递依赖与冲突解决的核心逻辑。

Java · 在线运行

观察重点

  • 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 Scopecompile/test/provided/runtime
传递依赖自动引入依赖的依赖
冲突解决最短路径优先,同深度先声明优先
Maven 生命周期clean / default / site 三套
Maven 仓库本地 → 私服 → 中央
Gradle DSLGroovy/Kotlin,简洁灵活
Gradle scopeimplementation/api/compileOnly/runtimeOnly

记忆口诀

  • 坐标三要素——groupId、artifactId、version。
  • scope 四档——compile(默认)、test(测试)、provided(容器提供)、runtime(运行时)。
  • 冲突最短路径优先——同深度先声明优先。
  • Maven 三套生命周期——clean、default、site 互不影响。
  • Gradle 比 Maven 快——增量编译 + 缓存。
  • Android 强制 Gradle——其他看团队。

结语:从代码到工程

这一章我们看了 Maven 和 Gradle——把”一堆 .java 文件”组织成”可构建、可依赖、可发布的工程”。下一步——Git 版本控制,让多人协作不再互相覆盖。