跳到主内容
创建时间: 2026-09-16最后更新: 2026-09-16

上一章通过 Spring Initializr 创建了 spring 项目,并使用 ./mvnw spring-boot:run 启动应用。我们虽然没有在系统中单独安装 Maven,却已经借助 Maven 完成了依赖下载、源码编译和应用启动。

Maven 是 Java 项目的构建与依赖管理工具。它不负责处理 HTTP 请求,也不读取 application.properties 中的端口;它负责把项目源码、资源和依赖组织成可以运行或交付的构建产物。

1. Maven

Java 源文件不能直接变成 Spring Boot 应用。一个完整构建通常需要处理资源、编译主代码、编译测试、运行测试并打包 JAR。Maven 为这些步骤提供统一的项目模型和生命周期。

Maven 中经常出现下面几个概念:

概念当前项目中的例子含义
POMpom.xml描述项目、依赖、插件和构建配置
坐标cn.usehook:spring:0.0.1-SNAPSHOT唯一标识一个 Maven 构建产物
依赖spring-boot-starter-webmvc当前项目编译或运行时需要的库
插件spring-boot-maven-plugin执行编译、测试、打包或运行等构建任务
生命周期compile、test、package约定构建阶段及其执行顺序
仓库Maven Central、本地仓库保存 POM、JAR 和插件

执行 ./mvnw spring-boot:run 时,各部分按照下面的顺序协作:

execution.txt
1
mvnw
2
→ Maven Wrapper 准备指定版本的 Maven
3
→ Maven 读取 pom.xml
4
→ 解析项目坐标、依赖和插件
5
→ 从本地仓库或远程仓库取得所需文件
6
→ 编译项目
7
→ spring-boot-maven-plugin 启动应用

Maven 遵循「约定优于配置」。如果没有额外修改,它会从 src/main/java 查找主代码,从 src/main/resources 读取资源,从 src/test/java 查找测试,并把构建结果写入 target。Spring Initializr 生成的项目正是这种标准结构。

2. Maven Wrapper

Maven Wrapper 让每个项目记录自己使用的 Maven 版本。项目成员只需要安装 JDK,就可以通过仓库中的 Wrapper 脚本启动一致的 Maven,不必依赖电脑上全局安装的 mvn。

Wrapper 由三个部分组成:

文件平台职责
mvnwmacOS、Linux启动 Wrapper 的 Shell 脚本
mvnw.cmdWindows启动 Wrapper 的批处理脚本
.mvn/wrapper/maven-wrapper.properties所有平台保存 Maven 发行版和 Wrapper 配置

maven-wrapper.properties 的典型内容如下。版本号应以项目实际生成的文件为准:

.mvn/wrapper/maven-wrapper.properties
1
wrapperVersion=3.3.4
2
distributionType=only-script
3
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip
配置项作用
wrapperVersion生成当前 Wrapper 脚本时使用的 Maven Wrapper 版本
distributionTypeWrapper 的实现形式;only-script 不需要提交 maven-wrapper.jar
distributionUrlMaven 二进制发行包的下载地址,也是实际锁定 Maven 版本的位置

还可以设置 distributionSha256Sum,让 Wrapper 验证 Maven 发行包的 SHA-256 摘要。它不是运行项目的必填项,但可以在持续集成环境中确认下载内容与预期一致。

第一次执行 Wrapper 时,它会下载 distributionUrl 指向的 Maven,并缓存到 Maven 用户目录的 wrapper/dists。后续执行通常直接复用缓存。项目依赖不保存在这个目录,而是由 Maven 下载到本地仓库。

根据操作系统检查当前项目实际使用的 Maven:

macOS-Linux.bash
Windows.ps1
1
./mvnw --version

输出会同时包含 Maven 版本、Java 版本和运行平台。Maven 版本应该与 distributionUrl 一致;Java 版本来自当前终端环境,因为 Wrapper 只管理 Maven,不负责安装或选择 JDK。

mvnw、mvnw.cmd 和 .mvn/wrapper/maven-wrapper.properties 都应该提交到 Git。团队和持续集成环境应优先执行 Wrapper,避免不同机器的全局 Maven 版本造成构建差异。

3. POM

POM 是 Project Object Model 的缩写。Maven 在项目根目录读取 pom.xml,再结合父 POM 和 Maven 默认配置,得到最终的项目模型。

spring 项目的主要配置如下:

pom.xml
01
<?xml version="1.0" encoding="UTF-8"?>
02
<project xmlns="http://maven.apache.org/POM/4.0.0"
03
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
04
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
05
https://maven.apache.org/xsd/maven-4.0.0.xsd">
06
<modelVersion>4.0.0</modelVersion>
07
08
<parent>
09
<groupId>org.springframework.boot</groupId>
10
<artifactId>spring-boot-starter-parent</artifactId>
11
<version>4.1.1</version>
12
<relativePath />
13
</parent>
14
15
<groupId>cn.usehook</groupId>
16
<artifactId>spring</artifactId>
17
<version>0.0.1-SNAPSHOT</version>
18
<name>spring</name>
19
<description>Spring Boot learning project</description>
20
21
<properties>
22
<java.version>21</java.version>
23
</properties>
24
25
<dependencies>
26
<dependency>
27
<groupId>org.springframework.boot</groupId>
28
<artifactId>spring-boot-starter-webmvc</artifactId>
29
</dependency>
30
31
<dependency>
32
<groupId>org.springframework.boot</groupId>
33
<artifactId>spring-boot-starter-webmvc-test</artifactId>
34
<scope>test</scope>
35
</dependency>
36
</dependencies>
37
38
<build>
39
<plugins>
40
<plugin>
41
<groupId>org.springframework.boot</groupId>
42
<artifactId>spring-boot-maven-plugin</artifactId>
43
</plugin>
44
</plugins>
45
</build>
46
</project>

modelVersion 表示当前 POM 使用的模型版本。Maven 3 项目通常使用 4.0.0,它不是当前项目的版本。

groupId、artifactId 和 version 共同组成项目坐标:

coordinate.txt
1
cn.usehook:spring:0.0.1-SNAPSHOT

groupId 表示组织或项目组,artifactId 是项目和默认构建产物的名称,version 是版本。SNAPSHOT 表示这个版本仍处于开发阶段。执行打包后,默认产物名称为 target/spring-0.0.1-SNAPSHOT.jar。

Maven 坐标不等于 Java 包名。groupId=cn.usehook 不会强制源码必须放在 cn.usehook 包中,只是项目通常采用相近命名,方便识别代码归属。

parent 声明当前项目继承 spring-boot-starter-parent。父 POM 提供常用依赖版本、插件版本和构建默认值,因此 Web Starter 和 Spring Boot 插件不需要在当前 POM 中重复填写版本。<relativePath /> 表示不从本地相对路径寻找父项目,而是通过仓库解析父 POM。

<java.version>21</java.version> 是 Spring Boot Parent 识别的属性,用于配置 Java 编译版本。它不会安装 JDK,实际执行 Maven 的机器仍然需要准备兼容的 Java 开发环境。

可以查看 Maven 合并父 POM 和默认值后的最终配置:

terminal.bash
1
./mvnw help:effective-pom

当代码中没有显式配置某个版本或插件行为时,effective-pom 可以帮助定位配置究竟来自当前 POM、Spring Boot Parent 还是 Maven 默认值。

4. 依赖

dependencies 中声明项目编译、运行或测试需要的库。一个依赖由 groupId、artifactId、version 和可选的 scope 标识。版本可以由当前 POM 直接指定,也可以由父 POM 的依赖管理统一提供。

spring-boot-starter-webmvc 是直接依赖,但它还依赖 Spring MVC、Jackson 和内嵌服务器等组件。Maven 会继续解析这些间接需要的库,这些库称为传递依赖。

使用下面的命令可以查看完整依赖树:

terminal.bash
1
./mvnw dependency:tree

依赖的 scope 决定它在哪些阶段可用:

scope编译主代码运行应用编译和运行测试常见用途
compile是是是默认作用域,业务代码直接使用的库
runtime否是是数据库驱动等运行时实现
test否否是测试框架和测试 Starter
provided是否是由目标运行环境提供的 API

Web Starter 没有显式 scope,所以使用默认的 compile。spring-boot-starter-webmvc-test 使用 test,不会成为应用正常运行所需的依赖。

dependencyManagement 与 dependencies 的作用不同。前者集中约束依赖版本,本身不会把依赖加入项目;后者才表示当前项目真正需要某个依赖。Spring Boot Parent 已经管理了大量常用库的兼容版本,添加这些库时通常不应该自行覆盖版本。

当某个传递依赖确实不应该进入项目时,可以在直接依赖中排除它:

pom.xml
01
<dependency>
02
<groupId>example</groupId>
03
<artifactId>example-starter</artifactId>
04
<version>1.0.0</version>
05
<exclusions>
06
<exclusion>
07
<groupId>example</groupId>
08
<artifactId>unused-library</artifactId>
09
</exclusion>
10
</exclusions>
11
</dependency>

排除依赖会改变运行时 classpath,应该先通过 dependency:tree 确认来源和影响,而不是看到重复名称就随意排除。

5. 插件与生命周期

依赖为项目代码提供可调用的类型,插件为 Maven 提供可以执行的构建目标。spring-boot-maven-plugin 负责运行应用,并把普通 JAR 重新打包为包含启动信息和依赖的 Spring Boot 可执行 JAR。

Maven 的默认生命周期包含一系列按顺序执行的阶段。执行后面的阶段时,前面的阶段也会依次执行:

阶段主要工作
validate检查项目结构和必要信息
compile编译 src/main/java
test运行单元测试
package生成 JAR 或 WAR
verify执行额外检查,确认构建结果有效
install将产物安装到本地 Maven 仓库
deploy将产物发布到远程 Maven 仓库

例如,执行 package 会先完成 validate、资源处理、compile 和 test,然后才生成 JAR。clean 属于独立的清理生命周期,它会删除上一次构建的 target:

terminal.bash
1
./mvnw clean package

命令中的 spring-boot:run 不是生命周期阶段,而是插件目标,格式为「插件前缀:目标」:

terminal.bash
1
./mvnw spring-boot:run

Spring Boot Parent 已经配置了 repackage 执行。当当前 POM 声明 spring-boot-maven-plugin 后,执行 package 会生成可通过下面命令启动的 JAR:

terminal.bash
1
java -jar target/spring-0.0.1-SNAPSHOT.jar

6. Maven 仓库

Maven 仓库保存 POM、JAR 和插件。解析一个依赖时,Maven 会先检查本地仓库;本地不存在时,再从 Maven Central 等远程仓库下载。

默认本地仓库位置如下:

macOS-Linux.txt
Windows.txt
1
~/.m2/repository

仓库目录按照 groupId、artifactId 和 version 逐层组织。例如 cn.usehook:spring:0.0.1-SNAPSHOT 安装后的路径大致为:

repository.txt
1
cn/usehook/spring/0.0.1-SNAPSHOT/

执行 ./mvnw install 会把当前项目的构建产物放入本地仓库,供本机其他 Maven 项目引用。deploy 则用于发布到团队或公共远程仓库,不等于部署正在运行的 Spring Boot 服务。

~/.m2/settings.xml 是 Maven 的用户级配置,可以设置镜像、代理、认证信息和自定义本地仓库位置。它不应该用来声明当前项目必须具备的业务依赖;需要随项目共享的依赖和插件仍应写入 pom.xml。

本地仓库是 Maven 管理的缓存和构建输入,不建议手动复制或修改其中的 JAR。依赖下载损坏时,应先确认网络、镜像和校验错误,再针对明确的依赖重新解析。

7. 常用命令

下面这些命令覆盖了当前阶段最常用的 Maven 操作。示例使用 macOS 和 Linux 的 ./mvnw;Windows 将它替换为 .\mvnw.cmd 即可。

命令作用
./mvnw --version查看 Maven、Java 和运行环境
./mvnw compile编译主代码
./mvnw test编译并运行测试
./mvnw clean package清理并生成可执行 JAR
./mvnw spring-boot:run通过 Spring Boot 插件运行项目
./mvnw dependency:tree查看直接依赖和传递依赖
./mvnw help:effective-pom查看合并后的最终 POM
./mvnw install构建并安装到本地仓库

Maven 命令可以从左到右包含多个目标。例如 clean package 先执行清理生命周期的 clean,再执行默认生命周期直到 package。命令执行失败时,应从输出中找到第一个 BUILD FAILURE 对应的具体错误,再判断是 Java 编译、测试、依赖解析还是插件执行失败。