什么是 Maven?关于 Maven 的命令、依赖传递、聚合与继承
目录
九、Maven 命令
1. 执行 Maven 命令的三种方式
(1)右侧 Maven 窗口的 Lifecycle
(2)命令行窗口(定位到 pom.xml 所在目录)
(3)Windows 自带的 CMD
2. Maven 命令的生命周期
十、依赖传递(Dependency Transitivity)
1. 什么是依赖传递
举例:
2. 会传递 / 不会传递
4. WAR 包不能被其他项目依赖
十一、依赖排除与查看依赖链
1. 依赖排除(Exclusion)
方法 1:直接添加自己的依赖
方法 2:排除传递进来的依赖
2. 通过依赖图查看依赖链
方法 1:命令行查看依赖树
方法 2:使用 IDEA 的依赖图(图形界面)
十二、Maven聚合
十三、Maven 继承
九、Maven 命令
1. 执行 Maven 命令的三种方式
在实际项目中,我们通常有三种方式来手动执行 Maven 的编译、打包等命令
(1)右侧 Maven 窗口的 Lifecycle
在 IntelliJ IDEA 中,右侧有一个 Maven 工具栏,展开项目后,可以看到 Lifecycle 节点。
双击对应阶段(例如 clean、package、install)即可执行命令。
(2)命令行窗口(定位到 pom.xml 所在目录)
打开终端(或 IDEA 的 Terminal),进入包含 pom.xml 的目录,执行例如:
mvn clean packagemvn compilemvn install
在编译过程中,出现了:不再支持源选项 5。请使用 7 或更高版本。
这是因为 Maven 默认使用的 maven-compiler-plugin 插件版本较老,默认的 Java 语言级别是 1.5,而 JDK 17 已不再支持这么低的版本。
解决办法是在 pom.xml 中手动配置 Maven 编译插件:
<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>17</source> <!-- 指定 Java 源码版本 --><target>17</target> <!-- 指定编译输出版本 --><encoding>UTF-8</encoding></configuration></plugin></plugins></build>
这样 Maven 会使用与本地 JDK 一致的编译版本,避免不兼容问题。
然后就能编译成功了:
(3)Windows 自带的 CMD
在 Windows 环境下,也可以使用系统自带的 cmd,先进入项目路径,再执行对应的 Maven 命令:
cd D:\work\demo_web1
mvn clean install
出现
这个问题也是因为 Maven 自带的 maven-war-plugin 版本太老(2.2),和当前使用的 JDK 17 + Maven 3.8.8 存在不兼容问题。
所以在pom.xml文件中加入:
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-war-plugin</artifactId><version>3.3.2</version>
</plugin>
此时,再次运行:
Maven 的常用命令都有默认插件,不需要手动配置。但当使用高版本 JDK 或需要特殊构建行为时,手动在 pom.xml 中指定对应插件的版本和配置是非常推荐的做法,尤其是 maven-compiler-plugin 和 maven-war-plugin 两个,能避免 90% 的兼容性报错。
2. Maven 命令的生命周期
Maven 的命令执行遵循生命周期(Lifecycle),分为以下三大类:
生命周期 | 作用 |
clean | 清理项目(删除 target/ 目录) |
default | 构建项目(编译、测试、打包、安装、部署) |
site | 生成项目报告站点 |
在默认构建生命周期中,包含一系列阶段:
阶段(Phase) | 说明 |
validate | 验证项目是否正确 |
compile | 编译主代码 |
test | 编译并执行单元测试 |
package | 打包成 jar/war 文件 |
verify | 对集成测试结果进行验证 |
install | 将构建好的包安装到 本地仓库 |
deploy | 将包发布到远程仓库(如 Nexus) |
例如执行:
mvn package
实际上会自动执行:validate → compile → test → package 这些阶段。
而执行:
mvn install
会在打包完成后,将生成的 jar/war 安装到本地 Maven 仓库中,供其他项目依赖使用。
3. 常用 Maven 命令一览
命令 | 作用 |
mvn clean | 清理项目 |
mvn compile | 编译主程序 |
mvn test | 执行单元测试 |
mvn package | 打包项目 |
mvn install | 安装到本地仓库 |
mvn deploy | 发布到远程仓库 |
mvn dependency:tree | 查看依赖树 |
mvn spring-boot:run | 启动 Spring Boot 项目 |
4. 依赖范围(Scope)
在 Maven 中,依赖通过 <scope> 标签来控制依赖的使用范围。不同的范围决定依赖在 编译、测试、打包 过程中的可用性
Scope | 编译(main) | 测试(test) | 打包(运行) | 常见场景 |
compile(默认) | √ | √ | √ | 普通依赖 |
provided | √ | √ | × | Servlet API、Tomcat 等容器提供的 |
runtime | × | √ | √ | JDBC 驱动、反射加载类 |
test | × | √ | × | JUnit、Mockito |
system | √ | √ | × | 本地系统路径 jar(不从仓库下载) |
示例:
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.9.2</version><scope>test</scope></dependency>
即使所有依赖都设置为 compile,项目功能也能正常运行。
合理设置 scope 可以让依赖更清晰,避免打包时引入多余 jar。
使用依赖时,推荐直接在中央仓库搜索坐标粘贴,在IDE 输入也会自动提示 scope。
十、依赖传递(Dependency Transitivity)
Maven 的一大特色就是依赖传递机制。它能够让项目自动引入间接依赖,减少了大量重复的依赖声明。但同时,如果不加以控制,也可能带来版本冲突或引入不必要的包。
1. 什么是依赖传递
假设有两个模块:
A 模块
B 模块
A 想要使用 B 的功能,需要在 A 的 pom.xml 中引入 B:
<!-- A 的 pom.xml -->
<dependencies><dependency><groupId>com.example</groupId><artifactId>module-b</artifactId><version>1.0.0</version></dependency>
</dependencies>
A 依赖 B 时,必须确保 B 先被安装到本地仓库(mvn install),否则 A 会找不到该依赖。
举例:
我把 demo_web1 项目当作A模块,hello 项目当作B模块
在hello 的pom.xml中添加:
<dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>8.3.0</version></dependency>
此时:
然后 install hello
再去 demo_web1 里面添加 hello 的依赖:
此时依赖就传递过来了
2. 会传递 / 不会传递
Maven 的依赖传递默认是开启的,也就是说,依赖默认会传递。
但有些特殊的 scope 会阻止依赖传递:
scope 值 | 是否传递依赖 | 说明 |
compile(默认) | 会传递 | 常规依赖,默认行为 |
provided | 不会传递 | 表示运行时由容器(如 Tomcat)提供,编译期可用 |
test | 不会传递 | 仅在测试代码中使用 |
runtime | 会传递 | 编译时不需要,运行时需要 |
system | 不会传递 | 本地系统路径依赖,不会传递 |
例如:
在 hello 里面添加依赖:
但是在 demo_web1 里面没有出现相关依赖:
3. <optional> 控制传递
除了 scope,还可以使用 <optional> 来显式控制依赖是否被传递:
<dependency><groupId>com.example</groupId><artifactId>some-lib</artifactId><version>1.0.0</version><optional>true</optional>
</dependency>
<optional>true</optional>:不传递
<optional>false</optional>(默认):传递
小技巧:optional 通常用于避免对下游项目强制引入一些不必要的库。
4. WAR 包不能被其他项目依赖
一个常见的误区是:将某个 Web 项目(<packaging>war</packaging>)当成依赖去引用,这是 不被 Maven 允许的
<!-- 错误:A 不能依赖一个 war 类型的模块 -->
<dependency><groupId>com.example</groupId><artifactId>web-module</artifactId><version>1.0.0</version><type>war</type>
</dependency>
原因:
WAR 是部署包,而不是库包(Jar)
WAR 包包含完整的 Web 应用结构(如 /WEB-INF/lib、HTML、JSP、Servlet 配置等),是要部署到 Web 容器(Tomcat、Jetty)中运行的。
而依赖机制是为编译或运行时加载类和资源设计的,依赖的应该是 jar 类型的可复用代码。
Maven 构建时不会把 WAR 当作 classpath 依赖
它不会把 WAR 包中的 WEB-INF/classes 或 lib 自动加到编译路径,因此引用 WAR 会导致编译/打包出错。
正确做法:
如果希望共享 WAR 中的业务逻辑,应该将公共代码从 WAR 中抽取成一个 独立的 Jar 模块:
common-utils(jar) ← 公共业务逻辑↑web-app(war)admin-app(war)
两个 Web 项目都依赖这个 common-utils 模块,这样结构清晰,依赖关系合理。
十一、依赖排除与查看依赖链
1. 依赖排除(Exclusion)
场景:传递进来的依赖版本过旧,或者与你的版本冲突
例如:
我的 demo_web1中,传递引入了mysql-connector-j:8.3.0
方法 1:直接添加自己的依赖
<dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>9.1.0</version></dependency>
那么此时:
Maven 会采用 “就近原则” + “声明优先”:
如果同一个依赖在多个地方出现,会优先使用你项目自己声明的版本,覆盖掉传递进来的版本。
方法 2:排除传递进来的依赖
如果确定不希望三方包传递 MySQL,可以这样写:
此时就没有了:mysql-connector-j:8.3.0
这样就能精准控制依赖的内容,避免版本冲突。
2. 通过依赖图查看依赖链
当项目依赖越来越多时,有些依赖并不是你直接引入的
方法 1:命令行查看依赖树
在项目根目录执行:
mvn dependency:tree
这样就能一眼看到是哪一条依赖链引入了不需要的依赖。
方法 2:使用 IDEA 的依赖图(图形界面)
在 IntelliJ IDEA 里,也可以更直观地查看依赖关系:
右键点击 pom.xml → Diagrams → Show Diagram
就会显示依赖图
打开依赖图后,按下 Ctrl + F 搜索你要排查的依赖
IDEA 会高亮出它的来源,你可以沿着箭头反查是谁带进来的
想单独查看某个依赖链,可以在图中点击该依赖,选择Show Paths 或 Show Neighbours of Selected Nodes
IDEA 就会只显示这条链路
十二、Maven聚合
Maven 聚合是一种用于管理多个子项目的项目组织方式。聚合项目本身不包含业务代码,它的主要作用是统一管理构建和模块顺序。
首先,先创建一个父项目,parent
可以删除 src目录,因为聚合项目通常不编写实际代码,因此不需要 src 目录。
聚合项目的 pom.xml 需要将 packaging 修改为 pom:
<packaging>pom</packaging>
在parent里面创建两个模块,aChild 和bChild
此时,要管理子模块,通过 <modules> 标签声明子项目
对于项目间的依赖就不用再install,就能感知到另一个项目的依赖
我在aChild 的 pom.xml 里面添加 bChild 的坐标信息
在bChild的pom.xml中引入依赖:
此时,刷新Maven,aChild中同样引入了依赖
当执行父聚合项目的编译命令(如 mvn clean install)时,所有子项目会按顺序编译。
十三、Maven 继承
Maven 继承是一种更高级的项目管理机制,它不仅管理模块,还能共享依赖、插件和配置。继承在功能上是聚合的增强版。
在 parent 里面添加了依赖,可以看到 aChild 和 bChild 都没有出现依赖
需要在两个子项目里面配置:
此时,就有了继承关系
同时,当使用了继承的方式,那么<groupId> 和 <version> 都会继承自父Maven
对于不是每个子项目都需要的依赖,就在父项目使用<dependencyManagement>
然后在子项目中,按需声明
或者在一开始创建项目的时候,选择好父项目,那么就会自动创建好相应的继承关系。