Maven 学习笔记
Maven 是 Java 项目中常用的项目管理与构建工具。它通过统一的项目结构和 pom.xml 配置文件,管理编译、测试、打包以及第三方依赖,使项目能够使用一组固定命令完成可重复构建。
这篇笔记以一个简单的命令行任务管理器为贯穿案例,重点掌握 pom.xml、项目结构、依赖管理、构建生命周期和常用命令。完成学习后,可以独立创建一个基础 Maven 项目,执行测试并生成可运行的 JAR 文件。
先验知识:能够阅读基础 Java 代码,了解 JDK、JAR 和命令行的基本概念即可。
示例基线
本文示例统一采用 Maven 3.9.16、JDK 17、JUnit Jupiter 5.14.4。Maven 4 尚未作为本文的学习基线,避免混用不同版本的配置方式。
目录
- 1. 应用背景
- 2. Maven 简介
- 3. POM 项目配置
- 4. Maven 项目结构
- 5. 依赖管理
- 6. 构建生命周期
- 7. 常用 Maven 命令
- 8. 完整项目案例
- 9. 常见问题与排查
- 10. 总结与速查
1. 应用背景
一个普通 Java 项目通常需要完成以下工作:
如果完全依靠手工操作,开发者需要自己下载 JAR、维护类路径、组织编译命令并处理依赖版本冲突。项目规模增大后,这些操作容易出错,也难以在其他电脑上重复执行。
Maven 将这些信息写入项目根目录的 pom.xml,再通过统一命令执行构建。例如:
# 在包含 pom.xml 的项目根目录执行
mvn clean package
这条命令会清理旧结果、编译源码、执行测试并生成项目制品。对普通 Java 项目而言,制品通常是 JAR 文件。
Maven 在工程中的位置可以概括为:
Java 源代码与资源文件
↓
pom.xml 描述项目、依赖和构建规则
↓
Maven 按生命周期调用不同插件
↓
编译结果、测试报告和 JAR 文件进入 target 目录
Maven 不替代 JDK。JDK 提供 Java 编译器和运行环境,Maven 负责组织并调用这些工具。IDE 可以集成 Maven,但项目是否能够正确构建,最终仍应以 Maven 命令的执行结果为准。
2. Maven 简介
Maven 是面向项目的构建与依赖管理工具。它读取 pom.xml,根据其中声明的项目信息、依赖和插件配置,完成编译、测试、打包、安装和发布等任务。
Maven 的核心特点包括:
- 约定优于配置:默认从 src/main/java 读取正式源码,从 src/test/java 读取测试源码,将构建结果写入 target;
- 声明式配置:开发者主要描述“项目需要什么”,而不是逐条编写所有底层编译命令;
- 依赖自动解析:根据依赖坐标从仓库下载 JAR,并处理传递依赖;
- 标准构建流程:不同项目可以使用 mvn test、mvn package、mvn verify 等统一命令;
- 插件扩展:编译、测试、打包等实际工作由 Maven 插件完成。
2.1 Maven 的输入、处理与输出
| 输入 | pom.xml、Java 源码、测试代码、资源文件 |
| 处理 | 解析项目模型、下载依赖、执行插件、依次完成生命周期阶段 |
| 输出 | .class 文件、测试报告、JAR/WAR、安装到本地仓库的制品 |
2.2 Maven、JDK、IDE 和仓库的关系
| JDK | 编译和运行 Java 程序 | Maven 调用 JDK 中的 javac 等工具 |
| IDE | 编辑、调试和运行代码 | IDE 可以读取并同步 Maven 项目 |
| pom.xml | 描述当前项目 | Maven 的核心输入文件 |
| Maven 插件 | 执行编译、测试、打包等任务 | Maven 生命周期通过插件目标完成实际工作 |
| Maven 仓库 | 保存项目依赖和构建制品 | Maven 从仓库解析依赖,并可向仓库安装或发布制品 |
判断项目是否真正可构建
IDE 中能够运行不代表 Maven 构建一定成功。提交代码前应在项目根目录执行 mvn clean verify。
3. POM 项目配置
POM 是 Project Object Model 的缩写,即项目对象模型。pom.xml 是 Maven 项目的核心配置文件,通常位于项目根目录。执行 Maven 命令时,Maven 默认在当前目录寻找该文件。
本笔记使用的案例项目名为 task-manager,目标是创建一个简单的命令行任务管理器。下面的 POM 同时配置了:
- 项目坐标;
- Java 17 编译版本;
- JUnit 测试依赖;
- 编译插件;
- 测试插件;
- 可执行 JAR 的主类。
文件位置:task-manager/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<!– Maven 项目的核心配置文件,文件名必须为 pom.xml –>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<!– 当前 POM 使用的模型版本,不是 Maven 软件版本 –>
<modelVersion>4.0.0</modelVersion>
<!– groupId、artifactId、version 共同唯一标识一个项目制品 –>
<!– 1. groupId 代表项目归属,遵循反向域名小写规范,用来区分团队/组织。
2. artifactId 是当前模块名称,小写并用横杠分隔单词,同一组织内保持唯一。
3. version 为项目版本,采用 主.次.补丁 三段数字格式,SNAPSHOT代表未发布快照版本。–>
<groupId>com.example</groupId>
<artifactId>task-manager</artifactId>
<version>1.0.0</version>
<!– jar 表示项目最终打包为 JAR;不写时默认也是 jar–>
<packaging>jar</packaging>
<properties>
<!–集中保存可复用值
1.编译基础配置:指定项目使用 JDK26 编译,所有源码文件编码统一为 UTF-8,解决版本不兼容、中文乱码问题。
2.依赖版本变量:把 JUnit 单元测试框架版本存入变量,后续引入测试依赖时直接引用,方便统一升级。
3.Maven 插件版本变量:集中定义编译、单元测试打包、Jar 包打包等核心插件的版本,修改版本只需改动此处,无需多处修改。 –>
<!– 使用 JDK 26 的 API 和字节码级别编译项目 –>
<maven.compiler.release>26</maven.compiler.release>
<!– 统一源码和资源文件编码,避免不同系统出现中文乱码 –>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!– 集中维护依赖与插件版本,后续升级只修改一处 –>
<junit.version>5.14.4</junit.version>
<maven.compiler.plugin.version>3.15.0</maven.compiler.plugin.version>
<maven.surefire.plugin.version>3.5.4</maven.surefire.plugin.version>
<maven.jar.plugin.version>3.5.1</maven.jar.plugin.version>
</properties>
<dependencies>
<!– `<dependencies>` 是 Maven 的依赖管理标签,用于声明项目需要引入的第三方工具类库 –>
<!– JUnit Jupiter 仅用于编译和执行测试,不进入正式运行环境 –>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<!–Maven 的 `<build>` 标签用来配置项目构建全过程,包含编译、资源文件复制、单元测试、打包、输出目录、插件等所有构建相关规则。
– maven-compiler-plugin:编译 Java 源码;
– maven-surefire-plugin:自动执行 JUnit 单元测试;
– maven-jar-plugin:打包可执行 Jar 包,内置主类配置支持命令行直接运行程序
– exec-maven-plugin:程序快速运行,执行 `mvn exec:java` 命令时,直接运行主类,无需先打包 jar,开发调试时快速启动程序。
–>
<plugins>
<!– 明确指定 Java 编译插件版本,避免依赖 Maven 默认版本 –>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.plugin.version}</version>
</plugin>
<!– 在 test 阶段发现并执行 JUnit 测试 –>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven.surefire.plugin.version}</version>
</plugin>
<!– 在 JAR 清单中写入主类,使生成的 JAR 可以通过 java -jar 运行 –>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven.jar.plugin.version}</version>
<configuration>
<archive>
<manifest>
<mainClass>com.example.task.App</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.1 项目坐标
Maven 使用坐标定位项目和依赖,最核心的三个字段是:
| groupId | com.example | 项目所属组织,通常使用反向域名 |
| artifactId | task-manager | 当前项目或模块名称 |
| version | 1.0.0 | 当前制品版本 |
三者组合后得到:
com.example:task-manager:1.0.0
Maven 构建完成后,JAR 文件名默认由 artifactId-version 组成:
task-manager-1.0.0.jar
开发中的不稳定版本常使用 1.0.0-SNAPSHOT。正式发布时通常去掉 SNAPSHOT,使用固定版本号。
3.2 属性与变量引用
properties 用于集中保存可复用值。POM 中通过 ${属性名} 引用属性:
<!– 在 properties 中集中定义版本 –>
<junit.version>5.14.4</junit.version>
<!– 在 dependency 中引用该版本 –>
<version>${junit.version}</version>
这样做可以减少重复,并使后续升级更明确。
maven.compiler.release 同时约束 Java 语言级别、生成的字节码版本以及可使用的标准库 API。它比只设置 source 和 target 更适合现代 JDK 项目。
3.3 依赖与插件
dependencies 和 build/plugins 容易混淆:
- 依赖供项目代码使用,例如 JUnit、数据库驱动、日志库;
- 插件在构建过程中执行任务,例如编译、测试、打包、生成文档。
本项目中的 JUnit 是测试依赖,maven-compiler-plugin、maven-surefire-plugin 和 maven-jar-plugin 是构建插件。
3.4 POM 的读取与验证
保存 pom.xml 后,在项目根目录执行:
# 检查 Maven 是否能够读取并解析当前 POM
mvn validate
正常情况下,终端最后出现:
BUILD SUCCESS
查看 Maven 合并默认配置、父 POM 和当前 POM 后得到的最终配置:
# 输出 Maven 实际使用的完整 POM
mvn help:effective-pom
pom.xml 与 settings.xml
pom.xml 属于项目,应提交到版本库;settings.xml 属于用户或构建机器,常用于配置镜像、私服和凭据,通常位于用户目录的 .m2/settings.xml,不应把密码直接写入项目 POM。
4. Maven 项目结构
Maven 默认使用固定目录约定,因此通常不需要在 POM 中逐项配置源码位置。
本案例采用以下结构:
task-manager/
├─ pom.xml # Maven 项目配置
├─ src/
│ ├─ main/
│ │ ├─ java/
│ │ │ └─ com/example/task/
│ │ │ ├─ App.java # 程序入口
│ │ │ └─ TaskService.java # 任务业务逻辑
│ │ └─ resources/ # 正式环境资源文件
│ └─ test/
│ ├─ java/
│ │ └─ com/example/task/
│ │ └─ TaskServiceTest.java # 单元测试
│ └─ resources/ # 测试资源文件
└─ target/ # Maven 自动生成的构建结果
各目录的默认用途如下:
| pom.xml | 项目、依赖和构建配置 | 是 |
| src/main/java | 正式 Java 源码 | 是 |
| src/main/resources | 配置、模板等正式资源 | 是 |
| src/test/java | 测试源码 | 是 |
| src/test/resources | 测试所需资源 | 是 |
| target/classes | 正式源码编译结果 | 否 |
| target/test-classes | 测试源码编译结果 | 否 |
| target/surefire-reports | 单元测试报告 | 否 |
| target/*.jar | 打包后的 JAR | 否 |
Java 文件的 package 必须与 src/main/java 或 src/test/java 后面的目录保持一致。例如:
// 文件路径:src/main/java/com/example/task/App.java
package com.example.task;
不要手动修改 target
target 是 Maven 自动生成目录,执行 mvn clean 后会被删除。源码和配置必须保存在 src 与 pom.xml 中。
5. 依赖管理
依赖管理解决的是“项目需要哪些外部类库、使用哪个版本、在什么阶段使用,以及从哪里取得”的问题。
5.1 依赖坐标与仓库
一个常见依赖声明包含:
<!– 声明一个仅用于测试的 JUnit 依赖 –>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.14.4</version>
<scope>test</scope>
</dependency>
Maven 解析依赖时通常按照以下过程工作:
读取 pom.xml 中的依赖坐标
↓
检查本地仓库是否已经存在
↓
不存在时访问远程仓库
↓
下载依赖及其元数据到本地仓库
↓
根据 scope 加入对应阶段的类路径
常见仓库包括:
- 本地仓库:当前电脑缓存的依赖,默认位于用户目录的 .m2/repository;
- 中央仓库:Maven 默认使用的公共远程仓库;
- 私有仓库:企业内部使用的 Nexus、Artifactory 等仓库。
本地仓库不是项目的 lib 目录。不要把 .m2/repository 整体复制到项目中,也不要手动修改其中的文件。
5.2 依赖范围
scope 决定依赖在哪些阶段可用,以及是否传递给下游项目。
| compile | 是 | 是 | 是 | 普通业务依赖,默认值 |
| runtime | 否 | 是 | 是 | 只在运行时需要的实现,例如部分数据库驱动 |
| test | 否 | 是 | 否 | JUnit、Mockito 等测试工具 |
| provided | 是 | 是 | 由运行环境提供 | Servlet API 等容器提供的类库 |
| import | 不直接形成类路径 | 不直接形成类路径 | 不直接形成类路径 | 在 dependencyManagement 中导入 BOM |
| system | 取决于配置 | 取决于配置 | 取决于配置 | 直接引用本机文件,不推荐使用 |
未填写 scope 时默认为 compile。
5.3 传递依赖
如果项目依赖 A,而 A 又依赖 B,Maven 通常会自动把 B 作为传递依赖加入项目。开发者不必手工声明每一层依赖。
查看完整依赖树:
# 在项目根目录查看直接依赖和传递依赖
mvn dependency:tree
输出会显示依赖层级、版本和范围。出现类冲突或版本不符合预期时,应优先执行这条命令。
如果某个传递依赖不应进入项目,可以排除:
<dependency>
<groupId>com.example</groupId>
<artifactId>example-library</artifactId>
<version>1.0.0</version>
<exclusions>
<!– 排除 example-library 间接引入的旧日志实现 –>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
排除后,如果项目仍然需要该类库,应在 dependencies 中显式声明合适版本。
5.4 版本冲突与依赖调解
同一个依赖通过多条路径进入项目时,可能出现多个版本。Maven 会进行依赖调解,通常优先选择依赖树中距离当前项目更近的版本;路径深度相同时,声明顺序也可能影响结果。
工程中不要依赖偶然的调解结果。更稳妥的做法是:
5.5 dependencies 与 dependencyManagement
dependencies 会真正把依赖加入当前模块;dependencyManagement 只提供统一的版本和默认配置,本身不会让依赖进入类路径。
<dependencyManagement>
<dependencies>
<!– 这里只统一版本,不会自动将 JUnit 加入当前模块 –>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.14.4</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!– 当前模块仍需显式声明依赖,但可以省略 version –>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
单模块小项目可以先使用 properties 集中维护版本。进入多模块项目后,再重点学习父 POM、dependencyManagement 和 BOM。
6. 构建生命周期
Maven 构建由三个概念组成:生命周期、阶段和插件目标。
- 生命周期(Lifecycle):完整构建过程的总体顺序;
- 阶段(Phase):生命周期中的一个步骤,例如 compile、test、package;
- 目标(Goal):插件执行的具体任务,例如 compiler:compile、surefire:test。
它们的关系是:
生命周期规定阶段顺序
↓
阶段绑定一个或多个插件目标
↓
插件目标执行实际工作
6.1 三个内置生命周期
| default | 编译、测试、打包、安装和发布项目 | compile、test、package、verify、install、deploy |
| clean | 删除旧构建结果 | clean |
| site | 生成项目站点和报告 | site |
普通开发最常使用 clean 和 default。
6.2 默认生命周期的关键阶段
默认生命周期包含多个阶段,初学阶段重点掌握以下顺序:
validate
↓
compile
↓
test
↓
package
↓
verify
↓
install
↓
deploy
| validate | 检查项目结构和配置是否可继续构建 | POM 被解析 |
| compile | 编译 src/main/java | target/classes |
| test | 编译并运行单元测试 | target/surefire-reports |
| package | 根据 packaging 生成 JAR 或 WAR | target/*.jar 或 target/*.war |
| verify | 执行打包后的质量检查 | 构建通过或失败 |
| install | 将制品写入本地仓库 | .m2/repository 中出现当前制品 |
| deploy | 将制品发布到远程仓库 | 团队或服务器可使用该制品 |
执行某个阶段时,Maven 会先执行它之前的所有阶段。例如:
# 会依次执行 validate、compile、test 和 package 等前置阶段
mvn package
因此,不需要先后手动执行 mvn compile、mvn test、mvn package。
6.3 阶段与插件目标的区别
以下两条命令含义不同:
# 执行默认生命周期直到 test 阶段
mvn test
# 直接调用 Surefire 插件的 test 目标
mvn surefire:test
日常构建优先执行生命周期阶段,因为它会保证前置步骤按顺序完成。直接调用插件目标通常用于诊断、工具操作或明确知道前置条件已经满足的场景。
7. 常用 Maven 命令
Maven 命令的基本格式是:
mvn [全局选项] [生命周期阶段或插件目标] [-D属性名=属性值]
例如:
# -U 是 Maven 全局选项,clean 和 verify 是生命周期阶段,skipTests 是传给插件的属性
mvn -U clean verify -DskipTests
其中:
- 生命周期阶段使用单个名称,例如 compile、test、package;
- 插件目标使用 插件前缀:目标,例如 dependency:tree、help:effective-pom;
- 全局选项控制 Maven 本次运行方式,例如 -X、-o、-P、-pl;
- -D 属性用于向 POM 或插件传入参数,例如 -DskipTests。
除创建新项目等特殊命令外,以下命令一般都在包含 pom.xml 的项目根目录执行。
优先使用 Maven Wrapper
如果项目根目录包含 mvnw、mvnw.cmd 和 .mvn,应优先使用 Wrapper,以保证团队使用同一 Maven 版本:Windows PowerShell 执行 .\\mvnw.cmd clean verify,Linux/macOS 执行 ./mvnw clean verify。下文的 mvn 均可替换为对应的 Wrapper 命令。
7.1 帮助与环境检查
Maven 版本与命令帮助
# 查看 Maven 版本、Maven 安装目录、Java 版本、Java 路径和操作系统信息
mvn -v
# 与 mvn -v 作用相同
mvn –version
# 查看 Maven 支持的全部命令行选项
mvn -h
# 显示 Maven 版本后继续执行构建,适合构建日志留档
mvn -V clean verify
排查构建环境时首先执行 mvn -v,重点检查:
- Maven 版本是否符合项目要求;
- Java version 是否正确;
- Java home 是否指向预期 JDK;
- Maven 是否使用了错误的用户目录或操作系统环境。
查看系统与环境属性
# 显示 Maven 能读取到的 Java 系统属性和环境变量
mvn help:system
该命令适合检查 JAVA_HOME、用户目录、文件编码和操作系统属性。输出可能包含较多环境信息,不要将包含敏感变量的完整结果直接公开。
查看最终 POM
# 查看继承、属性替换和 Profile 激活后的最终 POM
mvn help:effective-pom
# 在最终 POM 中标注每项配置来自哪个 POM,排查覆盖关系时更有用
mvn help:effective-pom -Dverbose
# 将最终 POM 输出到文件,便于搜索和对比
mvn help:effective-pom -Doutput=effective-pom.xml
当当前 pom.xml 中没有某项配置,但 Maven 实际使用了该配置时,通常是它来自父 POM、超级 POM、激活的 Profile 或插件默认值。
查看最终 Maven 设置
# 查看全局 settings.xml 与用户 settings.xml 合并后的实际配置
mvn help:effective-settings
# 将结果写入文件,便于检查镜像、私服和本地仓库配置
mvn help:effective-settings -Doutput=effective-settings.xml
该命令常用于排查:
- Maven 实际使用了哪个镜像;
- 私服地址是否生效;
- 本地仓库是否改过位置;
- 哪些服务器凭据配置被读取。
查看 Profile
# 查看当前构建已经激活的 Profile
mvn help:active-profiles
# 查看当前项目中全部可用 Profile,包括未激活的 Profile
mvn help:all-profiles
查询 Maven 表达式
# 读取当前项目版本号
mvn help:evaluate -Dexpression=project.version -q -DforceStdout
# 读取最终构建目录,默认通常为项目根目录下的 target
mvn help:evaluate -Dexpression=project.build.directory -q -DforceStdout
# 读取 Maven 实际使用的本地仓库路径
mvn help:evaluate -Dexpression=settings.localRepository -q -DforceStdout
-q 减少普通日志,-DforceStdout 强制把表达式结果输出到终端,适合脚本读取。
查看插件和目标参数
# 查看 Compiler 插件支持的目标和参数
mvn help:describe -Dplugin=org.apache.maven.plugins:maven-compiler-plugin -Ddetail
# 只查看 Compiler 插件 compile 目标的详细参数
mvn help:describe `
-Dplugin=org.apache.maven.plugins:maven-compiler-plugin `
-Dgoal=compile `
-Ddetail
当不知道某个插件参数如何填写时,优先使用 help:describe,不要凭印象猜测参数名。
7.2 生命周期基本命令
Maven 执行某个生命周期阶段时,会先执行该阶段之前的所有阶段。因此,通常只需要执行希望到达的最后阶段。
清理生命周期
# 删除当前项目的 target 目录和旧构建结果
mvn clean
clean 不会删除源码、pom.xml 或整个本地仓库,只清理当前项目生成的构建目录。
默认生命周期高频阶段
| mvn validate | 验证 POM 和项目基本信息 | 否 | 确认项目模型可被解析 |
| mvn compile | 编译正式源码 | 否 | target/classes |
| mvn test-compile | 编译正式源码和测试源码 | 不运行 | target/classes、target/test-classes |
| mvn test | 编译并运行单元测试 | 是 | target/surefire-reports |
| mvn package | 测试后生成 JAR/WAR | 是 | target/*.jar 或 target/*.war |
| mvn verify | 完成打包并执行额外质量验证 | 是 | 完整验证结果 |
| mvn install | 将当前制品安装到本地仓库 | 是 | .m2/repository 中出现当前制品 |
| mvn deploy | 将制品发布到远程仓库 | 是 | 团队私服或发布仓库中出现制品 |
对应命令如下:
# 只验证项目模型,不编译代码
mvn validate
# 编译 src/main/java 下的正式源码
mvn compile
# 编译正式代码和 src/test/java 下的测试代码,但不执行测试
mvn test-compile
# 编译并执行单元测试
mvn test
# 编译、测试并生成 JAR 或 WAR
mvn package
# 完成 package 之前的阶段,并执行项目配置的额外检查
mvn verify
# 完成构建并安装到当前电脑的 Maven 本地仓库
mvn install
# 完成构建并发布到 pom.xml 或 settings.xml 配置的远程仓库
mvn deploy
日常应如何选择
只检查单元测试使用 mvn test;需要构建文件使用 mvn package;提交代码前优先使用 mvn verify;只有其他本地项目需要引用当前项目时才使用 mvn install;mvn deploy 通常由发布流程或持续集成服务器执行。
经常在日志中出现的中间阶段
| initialize | 初始化构建状态和属性 |
| generate-sources | 生成需要参与编译的源码 |
| process-sources | 处理正式源码 |
| generate-resources | 生成资源文件 |
| process-resources | 将正式资源复制或处理到输出目录 |
| process-classes | 对编译后的类文件继续处理 |
| generate-test-sources | 生成测试源码 |
| process-test-resources | 处理测试资源 |
| prepare-package | 在正式打包前进行准备 |
| integration-test | 执行集成测试相关任务 |
| verify | 检查集成测试或其他质量规则的结果 |
这些阶段通常由插件绑定后自动执行。除调试插件绑定外,不需要逐个手动运行。
不要把 integration-test 当作完整集成测试命令
某些项目会在 pre-integration-test 启动测试环境,在 post-integration-test 关闭环境。只执行 mvn integration-test 可能无法完成清理,应执行 mvn verify 让后续阶段一起完成。
站点生命周期
# 根据项目报告配置生成 Maven Site
mvn site
# 将生成的站点发布到配置的目标位置
mvn site-deploy
# 先清理,再生成站点
mvn clean site
生成的站点通常位于 target/site。普通业务项目不一定配置站点,因此这组命令的使用频率低于 test、package 和 verify。
7.3 高频构建组合
实际开发通常会组合 clean 与默认生命周期阶段。
# 清理旧结果后重新编译正式代码
mvn clean compile
# 清理旧结果后执行全部单元测试
mvn clean test
# 清理、测试并生成 JAR 或 WAR
mvn clean package
# 清理并完成完整验证,提交代码前推荐使用
mvn clean verify
# 清理、完整构建并安装到本地仓库
mvn clean install
# 清理、完整构建并发布到远程仓库
mvn clean deploy
clean 与 package 属于不同生命周期,因此可以放在同一条命令中顺序执行。
不执行 clean 的情况
# 在现有 target 基础上继续构建,速度可能更快
mvn test
# 直接重新打包,不先删除 target
mvn package
Maven 本身不是真正的增量编译系统。修改插件配置、生成代码规则、资源文件或分支后,如果结果异常,应重新执行 mvn clean verify 排除旧文件干扰。
7.4 测试命令
以下命令适用于使用 Maven Surefire Plugin 执行的单元测试。
执行全部单元测试
# 编译并执行全部单元测试
mvn test
执行指定测试类
# 只执行 TaskServiceTest
mvn test -Dtest=TaskServiceTest
# 使用完整类名执行指定测试
mvn test -Dtest=com.example.task.TaskServiceTest
# 使用通配符执行名称匹配的测试类
mvn test -Dtest=*ServiceTest
# 执行多个指定测试类
mvn test -Dtest=TaskServiceTest,UserServiceTest
执行指定测试方法
# 只执行指定测试类中的一个方法
mvn test -Dtest=TaskServiceTest#addTaskShouldStoreValidTask
# 使用通配符执行名称匹配的方法
mvn test -Dtest=TaskServiceTest#addTask*
在 Windows PowerShell 中,# 可能被解释为注释起始符。出现命令被截断时,使用引号:
# 使用引号保护包含 # 的测试选择表达式
mvn test "-Dtest=TaskServiceTest#addTaskShouldStoreValidTask"
跳过测试
# 编译测试代码,但不执行测试
mvn clean package -DskipTests
# 测试代码既不编译也不执行
mvn clean package -Dmaven.test.skip=true
区别如下:
| -DskipTests | 是 | 否 | 临时构建时可用 |
| -Dmaven.test.skip=true | 否 | 否 | 仅在明确需要时使用 |
正式提交、合并和发布前应去掉跳过参数,重新执行 mvn clean verify。
集成测试
如果项目使用 Maven Failsafe Plugin 管理集成测试,可以使用:
# 执行包含集成测试结果验证的完整生命周期
mvn verify
# 只选择一个集成测试类;仅在项目配置了 Failsafe 时有效
mvn verify -Dit.test=UserApiIT
# 跳过集成测试,但是否仍执行单元测试取决于插件配置
mvn verify -DskipITs
普通项目没有配置 Failsafe 时,不要默认认为 -Dit.test 会生效。
7.5 依赖查看与分析
查看依赖树
# 显示项目实际解析后的完整依赖层级
mvn dependency:tree
# 只显示指定 groupId 下的依赖
mvn dependency:tree -Dincludes=org.junit.jupiter
# 只追踪某个具体依赖及其引入路径
mvn dependency:tree -Dincludes=org.slf4j:slf4j-api
# 排除不需要显示的依赖
mvn dependency:tree -Dexcludes=org.junit.jupiter
# 将依赖树写入文本文件
mvn dependency:tree -DoutputFile=dependency-tree.txt
依赖版本冲突、类重复、运行时找不到类时,应首先查看 dependency:tree,不要直接删除整个本地仓库。
查看扁平依赖列表
# 列出项目最终解析到的全部依赖,不显示树形父子关系
mvn dependency:list
# 只显示运行时需要的依赖
mvn dependency:list -DincludeScope=runtime
# 显示依赖在本地仓库中的绝对文件路径
mvn dependency:list -DoutputAbsoluteArtifactFilename=true
解析依赖但不构建项目
# 解析当前项目依赖,并显示解析结果
mvn dependency:resolve
# 解析当前项目构建所需插件
mvn dependency:resolve-plugins
# 提前解析依赖、插件和报告插件,为离线构建做准备
mvn dependency:go-offline
dependency:go-offline 能提前下载大量构建资源,但某些动态插件或项目脚本仍可能在离线运行时需要额外文件,因此应在断网前实际执行一次完整构建验证。
分析依赖声明
# 检查已声明但未使用,以及代码使用了但未直接声明的依赖
mvn dependency:analyze
# 检查现有 exclusions 是否已经失去作用
mvn dependency:analyze-exclusions
dependency:analyze 基于字节码分析,反射、SPI、注解处理器等依赖可能被误判为“未使用”,删除前必须结合项目实际用途确认。
生成运行类路径
# 在终端输出当前项目依赖组成的类路径
mvn dependency:build-classpath
# 将依赖类路径写入 cp.txt
mvn dependency:build-classpath -Dmdep.outputFile=cp.txt
该命令适合需要通过 java -cp 手动运行类的场景。
复制项目依赖
# 将当前项目的依赖复制到 target/dependency
mvn dependency:copy-dependencies
# 将运行时依赖复制到自定义目录
mvn dependency:copy-dependencies `
-DincludeScope=runtime `
-DoutputDirectory=target/lib
该命令只是复制依赖文件,不会自动让普通 JAR 变成包含全部依赖的可执行 Fat JAR。
单独下载一个制品
# 将指定制品及其传递依赖解析到本地仓库
mvn dependency:get -Dartifact=org.slf4j:slf4j-api:2.0.17
# 只下载指定制品,不解析传递依赖
mvn dependency:get `
-Dartifact=org.slf4j:slf4j-api:2.0.17 `
-Dtransitive=false
该命令适合验证坐标是否存在,或预先填充本地仓库。它不会自动把依赖写入当前项目的 pom.xml。
修复疑似损坏的本地依赖缓存
# 删除当前项目依赖在本地仓库中的缓存,并重新解析
mvn dependency:purge-local-repository
# 只清理 SNAPSHOT 依赖并重新解析
mvn dependency:purge-local-repository -DsnapshotsOnly=true
该命令会修改本地仓库,应在确认依赖缓存异常后使用。普通依赖冲突应先通过 dependency:tree 和 POM 版本管理解决。
查看项目使用的仓库
# 列出当前构建及传递依赖涉及的仓库
mvn dependency:list-repositories
如果依赖下载来源不符合预期,还应结合 mvn help:effective-settings 检查镜像配置。
7.6 Profile 与属性参数
激活 Profile
# 激活 dev Profile 后执行验证
mvn clean verify -Pdev
# 同时激活多个 Profile
mvn clean verify -Pdev,local
Profile 名称必须与 pom.xml 或 settings.xml 中的 <id> 一致。先执行 mvn help:all-profiles 可以查看项目提供了哪些 Profile。
传入用户属性
# 向 POM 或插件传入 env=dev 属性
mvn clean package -Denv=dev
# 同时传入多个属性
mvn clean package -Denv=dev -Dfeature.enabled=true
如果 POM 使用 ${env} 或插件声明了相同名称的用户属性,命令行值会参与本次构建。
-P 与 -D 不同
-Pdev 激活名为 dev 的 Profile;-Denv=dev 只是定义属性 env,只有 POM 或插件引用该属性时才产生效果。
7.7 离线、更新与仓库控制
# 强制检查缺失的 Release 和更新后的 SNAPSHOT
mvn -U clean verify
# 禁止检查 SNAPSHOT 更新,使用本地已有版本
mvn -nsu clean verify
# 完全离线构建,只使用本地仓库已有内容
mvn -o clean verify
三者区别:
| -U | 强制检查远程更新 | SNAPSHOT 未更新、失败缓存已修复 |
| -nsu | 不检查 SNAPSHOT 更新 | 临时减少远程请求 |
| -o | 禁止访问远程仓库 | 断网构建或验证本地缓存完整性 |
离线构建失败通常表示本地仓库缺少依赖或插件。联网时先执行:
# 提前解析离线构建所需内容
mvn dependency:go-offline
# 再实际完成一次构建,确认没有遗漏
mvn clean verify
7.8 日志与错误诊断
# 显示更完整的异常堆栈
mvn -e clean verify
# 输出最详细的调试日志
mvn -X clean verify
# 只显示错误信息,普通开发中不建议默认使用
mvn -q clean verify
# 将全部构建日志写入文件
mvn -l build.log clean verify
# 不显示依赖下载进度,持续集成日志更整洁
mvn -ntp clean verify
# 非交互批处理模式,适合 CI
mvn -B clean verify
推荐排错顺序:
-X 日志可能包含敏感信息
调试日志可能打印仓库地址、用户名、请求头或环境信息,公开前应先检查和脱敏。
7.9 指定 POM、settings 与执行目录
# 在当前目录之外,指定另一个 pom.xml 执行构建
mvn -f D:\\projects\\task-manager\\pom.xml clean verify
# 也可以把 -f 指向包含 pom.xml 的目录
mvn -f D:\\projects\\task-manager clean verify
# 使用指定的用户级 settings.xml
mvn -s D:\\maven-config\\settings.xml clean verify
# 使用指定的全局 settings.xml
mvn -gs D:\\maven-config\\global-settings.xml clean verify
适用场景:
- 自动化脚本不在项目目录运行;
- 不同公司私服需要不同 settings.xml;
- 临时验证某套镜像或代理配置;
- 同一机器维护多个 Maven 构建环境。
路径包含空格时应加引号:
# 路径含空格时使用引号
mvn -f "D:\\Java Projects\\task-manager\\pom.xml" clean verify
7.10 创建 Maven 项目
交互式创建
在准备存放项目的父目录执行:
# 打开 Archetype 列表并交互式选择项目模板
mvn archetype:generate
该方式会要求选择 Archetype、填写 groupId、artifactId、版本和包名,候选模板较多,初学者容易选错。
使用 Quickstart 模板创建基础项目
Windows PowerShell 可执行:
# 在当前目录生成 demo-app 项目,不进入交互式选择
mvn archetype:generate `
"-DgroupId=com.example" `
"-DartifactId=demo-app" `
"-DarchetypeGroupId=org.apache.maven.archetypes" `
"-DarchetypeArtifactId=maven-archetype-quickstart" `
"-DarchetypeVersion=1.5" `
"-DinteractiveMode=false"
Linux/macOS 可执行:
# 反斜杠表示命令换行,实际仍是一条命令
mvn archetype:generate \\
-DgroupId=com.example \\
-DartifactId=demo-app \\
-DarchetypeGroupId=org.apache.maven.archetypes \\
-DarchetypeArtifactId=maven-archetype-quickstart \\
-DarchetypeVersion=1.5 \\
-DinteractiveMode=false
生成后进入项目目录并验证:
# 进入新项目根目录
cd demo-app
# 执行模板自带的测试并完成构建验证
mvn clean verify
Archetype 适合快速获得基础目录,但真实工程通常使用团队模板、框架初始化工具或已有项目骨架。生成后仍需检查 POM 中的 Java、依赖和插件版本。
7.11 Maven Wrapper 命令
添加或更新 Wrapper
# 按当前 Maven 版本向项目加入 Wrapper 文件
mvn wrapper:wrapper
# 明确要求 Wrapper 使用 Maven 3.9.16
mvn wrapper:wrapper -Dmaven=3.9.16
执行后项目通常新增:
.mvn/
mvnw
mvnw.cmd
这些文件应提交到版本库,使其他成员无需预先安装完全相同的 Maven 版本。
使用 Wrapper 构建
# Windows PowerShell
.\\mvnw.cmd clean verify
# Linux/macOS
./mvnw clean verify
第一次执行 Wrapper 时可能需要联网下载指定 Maven 版本,后续会复用用户目录中的缓存。
7.12 多模块项目命令
假设父项目包含以下模块:
parent-project/
├─ pom.xml
├─ common/
├─ service/
└─ web/
以下命令应在父项目根目录执行。
构建指定模块
# 只构建 artifactId 为 service 的模块
mvn -pl :service clean verify
# 使用相对路径指定模块
mvn -pl service clean verify
同时构建模块依赖项
# 构建 service,并自动构建 service 依赖的同一 Reactor 内模块
mvn -pl :service -am clean verify
-am 是 –also-make,常用于 service 依赖 common 的情况。
同时构建依赖当前模块的下游模块
# 构建 common,并同时构建 Reactor 中依赖 common 的模块
mvn -pl :common -amd clean verify
-amd 是 –also-make-dependents,适合修改基础模块后验证受影响模块。
从失败模块继续
# 从 service 模块开始继续 Reactor 构建
mvn -rf :service verify
修复多模块构建中的失败模块后,可以根据 Maven 失败日志给出的提示使用 -rf,减少重复构建已成功模块。
只构建父项目本身
# 不递归进入子模块,只执行当前 POM
mvn -N validate
-N 是 –non-recursive,常用于只检查父 POM 或执行聚合层操作。
多线程构建
# 使用 4 个线程并行构建模块
mvn -T 4 clean verify
# 每个 CPU 核心使用 1 个构建线程
mvn -T 1C clean verify
多线程构建只适用于线程安全的插件。出现偶发失败、输出混乱或插件明确不支持并行时,应去掉 -T 重新验证。
多模块失败处理
# 第一个模块失败时立即停止,默认行为
mvn -ff clean verify
# 当前模块失败后,继续构建不受影响的模块,最后整体失败
mvn -fae clean verify
# 即使模块失败也不让 Maven 返回失败,不推荐用于正式流水线
mvn -fn clean verify
持续集成通常使用默认 -ff 或根据项目需要使用 -fae。-fn 会掩盖失败状态,应谨慎使用。
7.13 常用命令行选项速查
| -h | –help | 显示 Maven 命令帮助 |
| -v | –version | 显示版本后退出 |
| -V | –show-version | 显示版本并继续构建 |
| -Dname=value | –define | 定义用户属性 |
| -Pdev | –activate-profiles | 激活 Profile |
| -f | –file | 指定其他 POM 或项目目录 |
| -s | –settings | 指定用户 settings.xml |
| -gs | –global-settings | 指定全局 settings.xml |
| -U | –update-snapshots | 强制检查缺失 Release 和更新 SNAPSHOT |
| -nsu | –no-snapshot-updates | 禁止更新 SNAPSHOT |
| -o | –offline | 离线构建 |
| -B | –batch-mode | 非交互批处理模式 |
| -ntp | –no-transfer-progress | 隐藏下载和上传进度 |
| -q | –quiet | 只显示错误日志 |
| -e | –errors | 显示完整异常信息 |
| -X | –debug | 显示调试日志 |
| -l | –log-file | 将构建日志写入文件 |
| -T | –threads | 多线程构建 Reactor |
| -N | –non-recursive | 不递归构建子模块 |
| -pl | –projects | 选择要构建的模块 |
| -am | –also-make | 同时构建所选模块依赖的模块 |
| -amd | –also-make-dependents | 同时构建依赖所选模块的模块 |
| -rf | –resume-from | 从指定模块继续构建 |
| -ff | –fail-fast | 首次失败后停止,默认行为 |
| -fae | –fail-at-end | 继续不受影响模块,最后报告失败 |
| -fn | –fail-never | 始终不返回失败,不推荐用于正式构建 |
7.14 开发场景命令选择
| 刚拉取项目,确认能否正常构建 | mvn clean verify |
| 只检查正式代码能否编译 | mvn compile |
| 只检查测试代码能否编译 | mvn test-compile |
| 运行全部单元测试 | mvn test |
| 运行一个测试类 | mvn test -Dtest=类名 |
| 生成 JAR/WAR | mvn clean package |
| 提交代码前完整检查 | mvn clean verify |
| 让其他本地项目使用当前模块 | mvn clean install |
| 排查依赖版本冲突 | mvn dependency:tree |
| 检查依赖声明是否合理 | mvn dependency:analyze |
| 查看父 POM 和 Profile 合并结果 | mvn help:effective-pom -Dverbose |
| 查看镜像、私服和本地仓库设置 | mvn help:effective-settings |
| SNAPSHOT 没有更新 | mvn -U clean verify |
| 查看详细失败原因 | mvn -e clean verify |
| Maven 行为难以解释 | mvn -X clean verify |
| 构建指定模块及其依赖 | mvn -pl :模块名 -am clean verify |
| 修复失败模块后继续 | mvn -rf :模块名 verify |
| 持续集成构建 | mvn -B -ntp clean verify |
8. 完整项目案例
8.1 案例需求
创建一个命令行任务管理器,实现以下功能:
项目 POM 已在 3. POM 项目配置 中完成,本节直接创建源码和测试文件。
8.2 实现思路
TaskService 负责保存和校验任务,App 负责演示程序入口,TaskServiceTest 负责自动验证业务行为。
App
↓ 调用
TaskService
↓ 保存
内存中的 List<String>
TaskServiceTest
↓ 通过 JUnit 调用并断言
TaskService
该案例不引入运行时第三方依赖,因此 Maven 生成的 JAR 可以直接通过 java -jar 运行。JUnit 只在测试阶段使用。
8.3 任务服务
文件位置:src/main/java/com/example/task/TaskService.java
package com.example.task;
import java.util.ArrayList;
import java.util.List;
/**
* 管理任务的添加与查询。
* 当前示例使用内存保存数据,程序退出后数据不会持久化。
*/
public class TaskService {
// 使用 List 保存任务,以保留任务的添加顺序。
private final List<String> tasks = new ArrayList<>();
/**
* 添加一个任务。
*
* @param taskTitle 任务标题
* @throws IllegalArgumentException 当标题为 null、空字符串或仅包含空格时抛出
*/
public void addTask(String taskTitle) {
// 在业务入口统一校验,避免无效任务进入集合。
if (taskTitle == null || taskTitle.isBlank()) {
throw new IllegalArgumentException("任务标题不能为空");
}
// 去除标题首尾空格,使保存结果保持一致。
tasks.add(taskTitle.trim());
}
/**
* 返回当前全部任务。
*
* @return 不可修改的任务副本,避免调用方直接改变内部集合
*/
public List<String> listTasks() {
// List.copyOf 创建只读副本,保护 TaskService 内部状态。
return List.copyOf(tasks);
}
}
关键点:
- TaskService 不处理终端输入输出,只负责业务逻辑;
- isBlank() 能识别空字符串和只包含空格的字符串;
- trim() 统一清理任务标题两侧空格;
- List.copyOf() 防止外部代码直接修改内部任务列表。
8.4 程序入口
文件位置:src/main/java/com/example/task/App.java
package com.example.task;
/**
* 命令行任务管理器的程序入口。
*/
public class App {
public static void main(String[] args) {
// 创建任务服务,后续所有任务操作都通过该对象完成。
TaskService taskService = new TaskService();
// 添加两条示例任务,用于验证程序的基本功能。
taskService.addTask("学习 Maven POM");
taskService.addTask("执行 Maven 构建");
System.out.println("当前任务:");
// 按添加顺序输出任务,并从 1 开始显示序号。
for (int index = 0; index < taskService.listTasks().size(); index++) {
String task = taskService.listTasks().get(index);
System.out.printf("%d. %s%n", index + 1, task);
}
}
}
pom.xml 中的主类配置必须与这里的完整类名一致:
com.example.task.App
8.5 单元测试
文件位置:src/test/java/com/example/task/TaskServiceTest.java
package com.example.task;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* 验证 TaskService 的核心业务行为。
*/
class TaskServiceTest {
@Test
void addTaskShouldStoreValidTask() {
// 准备一个全新的服务,保证测试之间互不影响。
TaskService taskService = new TaskService();
// 添加前后带空格的任务,验证保存时会清理首尾空格。
taskService.addTask(" 学习 Maven ");
// 任务列表应只包含清理后的标题。
assertEquals(List.of("学习 Maven"), taskService.listTasks());
}
@Test
void addTaskShouldRejectBlankTitle() {
// 准备测试对象。
TaskService taskService = new TaskService();
// 空白标题必须抛出异常,避免无效数据进入任务列表。
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> taskService.addTask(" ")
);
// 同时验证异常信息,确保错误原因明确。
assertEquals("任务标题不能为空", exception.getMessage());
}
}
测试类默认放在 src/test/java。Surefire 通常会发现符合 *Test、Test* 等命名规则的测试类,并在 test 阶段执行。
8.6 执行测试
在 task-manager 根目录执行:
# 编译正式代码与测试代码,并执行全部单元测试
mvn test
正常结果应包含类似信息:
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS
测试报告位于:
target/surefire-reports/
验证重点:
- target/classes 中存在 App.class 和 TaskService.class;
- target/test-classes 中存在 TaskServiceTest.class;
- 测试统计为 2 个测试全部通过。
8.7 打包项目
# 删除旧 target,重新编译、测试并生成 JAR
mvn clean package
成功后应生成:
target/task-manager-1.0.0.jar
package 阶段会执行前面的编译和测试。只要测试失败,默认就不会生成成功的正式构建结果。
8.8 运行 JAR
Windows PowerShell、CMD、Linux 和 macOS 都可以在项目根目录执行:
# 运行 Maven 生成的可执行 JAR
java -jar target/task-manager-1.0.0.jar
预期输出:
当前任务:
1. 学习 Maven POM
2. 执行 Maven 构建
如果出现“没有主清单属性”,说明 JAR 中没有正确写入主类,应检查 maven-jar-plugin 中的 mainClass 是否与 App 的包名和类名一致。
8.9 案例闭环
本案例完成了 Maven 项目的基本工程流程:
编写 pom.xml
↓
按照约定创建源码和测试目录
↓
mvn test 执行自动化测试
↓
mvn clean package 生成 JAR
↓
java -jar 运行构建结果
真实项目可以在此基础上继续扩展数据库、日志、Web 框架和多模块结构,但 Maven 的核心流程仍然是 POM、依赖、生命周期和插件之间的配合。
9. 常见问题与排查
9.1 终端无法识别 mvn
问题表现
mvn: command not found
或:
'mvn' 不是内部或外部命令
检查方法
# 检查 Maven 命令是否进入 PATH
mvn -v
常见原因
- Maven 未正确配置到 PATH;
- 修改环境变量后,当前终端尚未重新打开;
- 项目本应使用 Maven Wrapper,但执行了全局 mvn。
解决方法
- 重新检查 Maven 的 bin 目录是否位于 PATH;
- 关闭并重新打开终端;
- 项目包含 mvnw.cmd 时,Windows PowerShell 可执行 .\\mvnw.cmd -v;Linux/macOS 可执行 ./mvnw -v。
9.2 Maven 使用了错误的 Java 版本
问题表现
release version 17 not supported
或构建时显示的 Java 版本与预期不同。
检查方法
# 同时查看 Maven 实际使用的 Java 版本和路径
mvn -v
# 查看终端默认 Java 版本
java -version
常见原因
- JAVA_HOME 指向旧 JDK;
- IDE 使用 JDK 17,但终端 Maven 使用另一套 JDK;
- 只修改了 PATH,没有同步修改 JAVA_HOME。
解决方法
使 mvn -v 中的 Java version 和 Java home 指向项目要求的 JDK 17,然后重新打开终端执行:
# 清理旧编译结果后重新验证
mvn clean verify
9.3 依赖无法下载
问题表现
Could not resolve dependencies
或:
Could not transfer artifact
检查方法
# 强制更新依赖并显示详细错误
mvn -U clean verify
解决方法
- 修正错误坐标或仓库地址;
- 企业项目使用正确的私服镜像;
- 删除本地仓库中该依赖对应目录里的失败缓存文件后重新构建;
- 不要在 POM 中写入明文仓库密码。
9.4 出现 package … does not exist
问题表现
package org.example.xxx does not exist
常见原因
- POM 未声明所需依赖;
- 依赖的 scope 错误,例如正式代码使用了 test 依赖;
- 依赖版本不包含当前使用的类;
- IDE 已手工添加 JAR,但 POM 没有对应声明。
检查方法
# 确认依赖是否真实进入 Maven 类路径
mvn dependency:tree
解决方法
将依赖正确声明在 pom.xml,调整 scope,然后执行:
# 清理 IDE 或旧构建产生的缓存影响
mvn clean compile
9.5 测试没有执行
问题表现
构建成功,但输出中显示:
Tests run: 0
常见原因
- 测试类放错目录;
- 测试类命名不符合 Surefire 默认发现规则;
- 使用了错误的 @Test 导入;
- POM 缺少 JUnit 依赖;
- 命令中使用了跳过测试参数。
检查方法
- 文件应位于 src/test/java;
- 当前案例测试类名应为 TaskServiceTest;
- 注解应导入 org.junit.jupiter.api.Test;
- 执行指定测试类确认。
# 明确要求执行当前测试类
mvn test -Dtest=TaskServiceTest
9.6 测试失败导致无法打包
问题表现
There are test failures
BUILD FAILURE
检查方法
- 查看终端中第一个失败测试;
- 打开 target/surefire-reports 中的文本或 XML 报告;
- 对比期望值与实际值;
- 单独执行失败测试方法。
# 只运行失败的方法,缩小排查范围
mvn test -Dtest=TaskServiceTest#addTaskShouldStoreValidTask
解决方法
修复业务代码或测试预期后重新执行 mvn clean verify。只有在临时确认非测试环节时才使用 -DskipTests,不要把跳过测试作为长期解决方式。
9.7 JAR 无法通过 java -jar 运行
问题表现
no main manifest attribute
或:
Could not find or load main class
检查方法
# 重新生成包含最新清单配置的 JAR
mvn clean package
9.8 修改配置后结果没有变化
常见原因
- 实际执行命令的目录不是当前项目根目录;
- 运行了旧的 target 文件;
- IDE 尚未重新加载 POM;
- 父 POM、Profile 或默认配置覆盖了当前设置。
检查方法
# 查看当前 Maven 真正使用的最终配置
mvn help:effective-pom
# 删除旧结果并完整验证
mvn clean verify
同时确认终端当前目录中存在正确的 pom.xml。
10. 总结与速查
10.1 核心关系
pom.xml
├─ 项目坐标:项目是谁
├─ properties:集中保存版本和参数
├─ dependencies:项目代码需要什么类库
└─ build/plugins:构建过程由哪些工具执行
Maven 生命周期
├─ compile:编译正式代码
├─ test:执行单元测试
├─ package:生成 JAR/WAR
├─ verify:执行完整验证
├─ install:安装到本地仓库
└─ deploy:发布到远程仓库
10.2 高频命令
| 环境 | 查看 Maven 与 Java 环境 | mvn -v |
| 帮助 | 查看命令行参数 | mvn -h |
| 配置 | 查看最终 POM | mvn help:effective-pom -Dverbose |
| 配置 | 查看最终 settings | mvn help:effective-settings |
| 配置 | 查看激活的 Profile | mvn help:active-profiles |
| 生命周期 | 清理构建结果 | mvn clean |
| 生命周期 | 编译正式源码 | mvn compile |
| 生命周期 | 编译测试源码 | mvn test-compile |
| 生命周期 | 执行单元测试 | mvn test |
| 生命周期 | 生成 JAR/WAR | mvn clean package |
| 生命周期 | 完整质量验证 | mvn clean verify |
| 生命周期 | 安装到本地仓库 | mvn clean install |
| 生命周期 | 发布到远程仓库 | mvn clean deploy |
| 测试 | 运行指定测试类 | mvn test -Dtest=TaskServiceTest |
| 测试 | 运行指定测试方法 | mvn test "-Dtest=TaskServiceTest#方法名" |
| 测试 | 跳过测试执行 | mvn package -DskipTests |
| 依赖 | 查看依赖树 | mvn dependency:tree |
| 依赖 | 分析依赖声明 | mvn dependency:analyze |
| 依赖 | 为离线构建预下载 | mvn dependency:go-offline |
| 依赖 | 重新解析项目依赖 | mvn dependency:purge-local-repository |
| 仓库 | 强制检查远程更新 | mvn -U clean verify |
| 仓库 | 离线构建 | mvn -o clean verify |
| Profile | 激活开发环境配置 | mvn clean verify -Pdev |
| 排错 | 显示完整异常 | mvn -e clean verify |
| 排错 | 显示调试日志 | mvn -X clean verify |
| Wrapper | Windows 统一版本构建 | .\\mvnw.cmd clean verify |
| Wrapper | Linux/macOS 统一版本构建 | ./mvnw clean verify |
| 多模块 | 构建指定模块及其依赖 | mvn -pl :模块名 -am clean verify |
| 多模块 | 从失败模块继续 | mvn -rf :模块名 verify |
| CI | 非交互且隐藏传输进度 | mvn -B -ntp clean verify |
10.3 依赖范围速查
| compile | 正式编译和运行都需要,默认值 |
| runtime | 编译主代码不需要,运行时需要 |
| test | 仅测试代码和测试执行需要 |
| provided | 编译需要,运行环境负责提供 |
| import | 在 dependencyManagement 中导入 BOM |
| system | 直接绑定本机 JAR,应避免使用 |
10.4 工程实践
- 将 pom.xml、src、.mvn、mvnw 和 mvnw.cmd 提交到版本库;
- 不提交 target、IDE 缓存和本地仓库;
- 明确固定关键依赖与插件版本,避免不同环境得到不同构建行为;
- 依赖冲突先执行 mvn dependency:tree,配置异常先执行 mvn help:effective-pom;
- 不手工修改 target,也不要把 IDE 手工添加的 JAR 当作 Maven 依赖;
- 多模块项目使用父 POM 和 dependencyManagement 统一版本;
- 团队项目优先使用 Maven Wrapper,减少成员本机 Maven 版本差异;
- 提交或合并代码前执行 mvn clean verify;
- 初学阶段先掌握单模块 JAR 项目,再学习父子模块、BOM、Profile、私服和发布流程。
最需要记住的一句话
Maven 通过 pom.xml 描述项目,按照生命周期调用插件,并从仓库解析依赖,最终在 target 中生成可验证、可重复的构建结果。




