《Maven 基础教程》系列,包含以下 5 篇文章:
- Maven 基础教程(一):基础介绍、开发环境配置
- Maven 基础教程(二):Maven 的使用
- Maven 基础教程(三):build、profile
- Maven 基础教程(四):搭建 Maven 私服 Nexus
- Maven 基础教程(五): jar 包冲突问题
😊 如果您觉得这篇文章有用 ✔️ 的话,请给博主一个一键三连 🚀🚀🚀 吧 (点赞 🧡、关注 💛、收藏 💚)!!!您的支持 💖💖💖 将激励 🔥 博主输出更多优质内容!!!
Maven 基础教程(二):Maven 的使用
3.Maven 的使用
3.1 核心概念:坐标
数学中的坐标使用
x
x
x、
y
y
y、
z
z
z 三个 向量 作为空间的坐标系,可以在 空间 中唯一的定位到一个 点。
Maven 中的坐标使用三个 向量 在 Maven的仓库 中唯一的定位到一个 jar 包。
groupId
:公司或组织的id
,即公司或组织域名的 倒序,通常也会加上项目名称。例如:com.javatv.maven
。artifactId
:一个项目或者是项目中的一个模块的id
,即模块的名称,将来作为 Maven 工程的工程名。例如:auth
。version
:版本号。例如:1.0.0
。
坐标和仓库中 jar
包的存储路径之间的对应关系,如下:
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
上面坐标对应的 jar
包在 Maven 本地仓库中的位置:
Maven本地仓库根目录\javax\servlet\servlet-api\2.5\servlet-api-2.5.jar
3.2 pom.xml
POM
:Project Object Model
,项目对象模型。和POM
类似的是DOM
(Document Object Model
),文档对象模型。它们都是模型化思想的具体体现。POM
表示将工程抽象为一个模型,再用程序中的对象来描述这个模型。这样我们就可以用程序来管理项目了。我们在开发过程中,最基本的做法就是将现实生活中的事物抽象为模型,然后封装模型相关的数据作为一个对象,这样就可以在程序中计算与现实事物相关的数据。POM
理念集中体现在 Maven 工程根目录下pom.xml
这个配置文件中。所以这个pom.xml
配置文件就是 Maven 工程的 核心配置文件。其实学习 Maven 就是学这个文件怎么配置,各个配置有什么用。
<!-- 当前Maven工程的坐标 -->
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<!-- 当前Maven工程的打包方式,可选值有下面三种:-->
<!-- jar:表示这个工程是一个Java工程 -->
<!-- war:表示这个工程是一个Web工程 -->
<!-- pom:表示这个工程是“管理其他工程”的工程 -->
<packaging>jar</packaging>
<properties>
<!-- 工程构建过程中读取源码时使用的字符集 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!-- 当前工程所依赖的jar包 -->
<dependencies>
<!-- 使用dependency配置一个具体的依赖 -->
<dependency>
<!-- 在dependency标签内使用具体的坐标依赖我们需要的一个jar包 -->
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<!-- scope标签配置依赖的范围 -->
<scope>test</scope>
</dependency>
</dependencies>
3.3 依赖
上面说到我们使用 Maven 最主要的就是使用它的依赖管理功能,引入依赖存在一个范围,Maven 的依赖范围包括:compile
,provided
,runtime
,test
,system
。
compile
:表示编译范围,指 A 在编译时依赖 B,该范围为默认依赖范围。编译范围的依赖会用在编译,测试,运行,由于运行时需要,所以编译范围的依赖会被打包。provided
:provied
依赖只有当jdk
或者一个容器已提供该依赖之后才使用。provided
依赖在编译和测试时需要,在运行时不需要。例如:Servlet API 被 Tomcat 容器提供了。runtime
:runtime
依赖在运行和测试系统时需要,但在编译时不需要。例如:JDBC 的驱动包。由于运行时需要,所以runtime
范围的依赖会被打包。test
:test
范围依赖在编译和运行时都不需要,只在测试编译和测试运行时需要。例如:Junit。由于运行时不需要,所以test
范围依赖不会被打包。system
:system
范围依赖与provided
类似,但是必须显示的提供一个对于本地系统中jar
文件的路径。一般不推荐使用。
依赖范围 | 编译 | 测试 | 运行时 | 是否会被打入 jar 包 |
---|---|---|---|---|
compile | ✅ | ✅ | ✅ | ✅ |
provided | ✅ | ✅ | ❌ | ❌ |
runtime | ❌ | ✅ | ✅ | ✅ |
test | ❌ | ✅ | ❌ | ❌ |
system | ✅ | ✅ | ❌ | ✅ |
而在实际开发中,我们常用的就是 compile
、test
、provided
。
3.4 依赖的传递
A 依赖 B,B 依赖 C,那么在 A 没有配置对 C 的依赖的情况下,A 里面能不能直接使用 C?在以上的前提下,C 是否能够传递到 A,取决于 B 依赖 C 时使用的依赖范围。B 依赖 C 时使用 compile
范围:可以传递;B 依赖 C 时使用 test
或 provided
范围:不能传递。所以需要这样的 jar
包时,就必须在需要的地方明确配置依赖才可以。
3.5 依赖的排除
当 A 依赖 B,B 依赖 C,而且 C 可以传递到 A 的时候,A 不想要 C,需要在 A 里面把 C 排除掉。而往往这种情况都是为了避免 jar
包之间的冲突。
所以配置依赖的排除其实就是阻止某些 jar
包的传递。因为这样的 jar
包传递过来会和其他 jar
包冲突。
一般通过使用 excludes
标签配置依赖的排除:
<dependency>
<groupId>net.javatv.maven</groupId>
<artifactId>auth</artifactId>
<version>1.0.0</version>
<scope>compile</scope>
<!-- 使用excludes标签配置依赖的排除 -->
<exclusions>
<!-- 在exclude标签中配置一个具体的排除 -->
<exclusion>
<!-- 指定要排除的依赖的坐标(不需要写version) -->
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
3.6 继承
3.6.1 概念
Maven 工程之间,A 工程继承 B 工程
- B 工程:父工程
- A 工程:子工程
本质上是 A 工程的 pom.xml
中的配置继承了 B 工程中 pom.xml
的配置。
3.6.2 作用
在父工程中统一管理项目中的依赖信息,具体来说是管理依赖信息的版本。
它的背景是:
- 对一个比较大型的项目进行了模块拆分。
- 一个
project
下面,创建了很多个module
。 - 每一个
module
都需要配置自己的依赖信息。
它背后的需求是:
- 在每一个
module
中各自维护各自的依赖信息很容易发生出入,不易统一管理。 - 使用同一个框架内的不同
jar
包,它们应该是同一个版本,所以整个项目中使用的框架版本需要统一。 - 使用框架时所需要的
jar
包组合(或者说依赖信息组合)需要经过长期摸索和反复调试,最终确定一个可用组合。这个耗费很大精力总结出来的方案不应该在新的项目中重新摸索。 - 通过在父工程中为整个项目维护依赖信息的组合既保证了整个项目使用规范、准确的
jar
包;又能够将以往的经验沉淀下来,节约时间和精力。
3.6.3 一个例子
1️⃣ 一般在模块化开发中一般都会创建一个父工程,如下:
父工程创建好之后,要修改它的打包方式:
<!-- 当前工程作为父工程,它要去管理子工程,所以打包方式必须是 pom -->
<packaging>pom</packaging>
只有打包方式为 pom
的 Maven 工程能够管理其他 Maven 工程。打包方式为 pom
的 Maven 工程中不写业务代码,它是专门管理其他 Maven 工程的工程,所以可以将生成的 src
目录删除。
2️⃣ 创建模块工程
然后可以在父工程的 pom
文件中看到:
而子工程的 pom
如下:
3️⃣ 在父工程中配置依赖的统一管理
使用 dependencyManagement
标签配置对依赖的管理,如下:
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.javatv.maven</groupId>
<artifactId>maven-demo-parent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>demo-module</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.19</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.19</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.19</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.19</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
而实际上被管理的依赖并没有真正被引入到工程。
4️⃣ 子工程中引用那些被父工程管理的依赖
关键点:省略版本号
子工程引用父工程中的依赖信息时,可以把版本号去掉。把版本号去掉就表示子工程中这个依赖的版本由父工程决定,具体来说是由父工程的 dependencyManagement
来决定。
子工程 pom
如下:
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- 使用parent标签指定当前工程的父工程 -->
<parent>
<artifactId>maven-demo-parent</artifactId>
<groupId>net.javatv.maven</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<!-- 子工程的坐标 -->
<!-- 如果子工程坐标中的groupId和version与父工程一致,那么可以省略 -->
<artifactId>demo-module</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
</dependencies>
</project>
此时,被管理的依赖才被引入到工程。
5️⃣ 修改父工程依赖信息的版本
这个修改可以是降级,也可以是升级,但一般来说都是升级。
6️⃣ 父工程中声明自定义属性
对同一个框架的一组 jar
包最好使用相同的版本,为了方便升级框架,可以将 jar
包的版本信息统一提取出来,统一声明版本号:
<!-- 通过自定义属性,统一指定Spring的版本 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 自定义标签,维护Spring版本数据 -->
<spring.version>5.3.19</spring.version>
</properties>
在需要的地方使用 ${}
的形式来引用自定义的属性名,真正实现一处修改,处处生效。
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.javatv.maven</groupId>
<artifactId>maven-demo-parent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>demo-module</module>
</modules>
<!-- 通过自定义属性,统一指定Spring的版本 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 自定义标签,维护Spring版本数据 -->
<spring.version>5.3.19</spring.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
编写一套符合要求、开发各种功能都能正常工作的依赖组合并不容易。如果公司里已经有人总结了成熟的组合方案,那么再开发新项目时,如果不使用原有的积累,而是重新摸索,会浪费大量的时间。为了提高效率,我们可以使用工程继承的机制,让成熟的依赖组合方案能够保留下来。如下:
如上图所示,公司级的父工程中管理的就是成熟的依赖组合方案,各个新项目、子系统各取所需即可。
3.7 聚合
聚合,指分散的聚集到一起,即部分组成整体。
3.7.1 Maven 中的聚合
使用一个总工程将各个模块工程汇集起来,作为一个整体对应完整的项目,实际就是 module
标签。
- 项目:整体
- 模块:部分
3.7.2 继承和聚合的对应关系
从 继承 关系角度来看:
- 父工程
- 子工程
从 聚合 关系角度来看:
- 总工程
- 模块工程
3.7.3 聚合的配置
在总工程中配置 modules
即可:
<modules>
<module>demo-module</module>
</modules>
3.7.4 依赖循环问题
如果 A 工程依赖 B 工程,B 工程依赖 C 工程,C 工程又反过来依赖 A 工程,那么在执行构建操作时会报下面的错误:
DANGER
[ERROR] [ERROR] The projects in the reactor contain a cyclic reference:
这个错误的含义是:循环引用。