Maven实战(二)- 坐标与依赖
1.何为坐标
Maven坐标为各种构件(就是平时用的jar,war等文件)引入了秩序,任何一个构件都必须明确定义自己的坐标,而一组Maven坐标是通过一些元素定义的,它们是groupId、artifactId、version、packaging、classifier。如下所示。
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.14</version>
<packaging>pom</packaging>
-
groupId: 当前Maven项目所属的实际项目。首先,Maven项目和实际项目不一定是一对一的关系。比如SpringFramework这一实际项目,其对应的Maven项目会有很多,如spring-core、spring-context等。这是由于Maven中模块的概念,因此,一个实际项目往往会被划分成很多模块。其次groupId不应该对应项目隶属的组织或者公司。一个组织下会有很多项目,如果groupId只定义到组织级别,而后面我们会看到,artifactId只能定义Maven项目(模块),那么实际项目这个层将难以定义。最后,groupId的表示方式与java包名的表示方式类似,通常是域名反向一一对应。
-
artifactId: 定义实际项目中的一个Maven项目(模块),官方推荐做法是使用实际项目名称作为artifactId的前缀。
-
version: 定义Maven项目当前所处的版本。
-
packaging: 定义Maven项目的打包方式。
-
classifier: 用来帮助定义构建输出的一些附属构件。附属构件与主构件对应,如下图所示,主构件为druid-1.2.20.jar,该项目可能还会通过一些插件生产如druid-1.2.20-sources.jar这样的附属构件,表示源代码。
2.依赖的配置
依赖声明包含以下的一些元素:
<project>
...
<dependencies>
<dependency>
<groupId></groupId>
<artifactId></artifactId>
<version></version>
<type></type>
<scope></scope>
<optional></optional>
<exclusions>
<exclusion></exclusion>
...
</exclusions>
</dependency>
...
</dependencies>
...
</project>
dependency
包含的元素有:
- groupId、artifactId、version: 依赖的基本坐标。
- type: 依赖的类型,对应于项目坐标定义的packaging。大部分情况下,该元素不必声明,默认值为jar。
- scope: 依赖的范围。
- optional: 标记依赖是否可选。
- exclusions: 用来排除依赖的传递性。
3.依赖范围
首先需要知道Maven在变异项目主代码的时候需要使用一套classpath。例如在开发一个spring项目时,编译项目主代码的时候用到了spring-core,该文件将以依赖的方式被引入到classpath中。其次,Maven在编译和执行测试的时候会使用另外一套classpath。做单元测试时,如JUnit,该文件也以依赖的方式引入到测试使用的classpath中,不同的是这里的依赖范围是test。最后,实际运行Maven项目的时候,又会使用一套classpath,spring-core需要在该classpath,而 JUnit则不需要。
依赖范围就是用来控制依赖与这三种classpth(编译classpath、测试classpath、运行classpath)的关系。Maven有以下几种依赖范围:
- compile: 编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的Maven依赖。对于编译、测试、运行三种classpath 都有效。
- test: 测试的依赖范围。只对测试classpath有效。
- provided: 已提供的依赖范围。对于编译classpath、测试classpath有效。
- runtime: 运行时的依赖范围。对于测试classpath、运行classpath有效。
- system: 系统依赖范围。对于编译classpath、测试classpath有效。system是一个比较特殊的依赖范围,使用此依赖范围时必须通过systemPath元素显示声明此依赖文件的路径。该依赖范围通常用于从本地文件系统引用依赖,而不是从Maven仓库中获取。由于这种方式的灵活性和可控性较差,因此不推荐使用。
- import: 导入依赖范围。它的作用是将其他模块定义好的 dependencyManagement 导入当前 Maven 项目 pom 的 dependencyManagement 中。
注意,依赖范围
import
只能在<dependencyManagement>
部分使用,不能在<dependencies>
部分使用。
上述除import以外的依赖范围的有效关系,如下表所示。
依赖范围 | 编译classpath有效 | 测试classpath有效 | 运行classpath有效 | 案例 |
---|---|---|---|---|
compile | Y | Y | Y | spring-core |
test | Y | JUnit | ||
provided | Y | Y | servlet-api | |
runtime | Y | Y | JDBC | |
system | Y | Y | 引入本地jar |
4.传递性依赖
Maven的传递性依赖是指当一个项目依赖于某个库(称为直接依赖),而该库本身又依赖于其他库(称为间接依赖)时,Maven会自动解析和下载这些间接依赖,并将其添加到项目的classpath中。
通过传递性依赖,我们可以方便地管理和引用项目所依赖的所有库和其它相关的依赖,而不需要手动处理每一个依赖及其相关的依赖。 Maven会基于项目的pom.xml
文件来解析传递性依赖。当我们在pom.xml
中声明了某个直接依赖后,Maven会自动检查该依赖所声明的其他依赖,并进行递归解析和下载。Maven会使用依赖解析算法来确定传递性依赖的版本和冲突处理。
使用传递性依赖时,需要注意以下几点:
- 版本冲突:当不同的直接依赖对同一个间接依赖使用了不同的版本时,Maven会使用一套冲突解析规则来解决冲突,包括优先级、最短路径等规则。
- 排除依赖:有时我们需要排除某些传递性依赖,可以在直接依赖的声明中使用
<exclusions>
标签来排除指定的依赖。 - 依赖范围:用于控制依赖在不同的构建阶段或环境中的可见性和使用。
依赖范围的设置会影响到传递性依赖的可见性和使用方式。例如,如果一个直接依赖的范围设置为provided,那么该依赖及其传递性依赖在运行时不会被包含在构建产物中。如果一个直接依赖的范围设置为test,那么该依赖及其传递性依赖只在测试阶段可见。如下图所示,列表示直接依赖,行表示间接依赖(直接依赖中的依赖)。
compile | test | provided | runtime | |
---|---|---|---|---|
compile | compile | runtime | ||
test | test | test | ||
provided | provided | provided | provided | |
runtime | runtime | runtime |
5.依赖调解
Maven依赖调解是指解决不同依赖版本冲突的过程。当一个项目依赖于多个库,而这些库又依赖于不同版本的同一个库时,就会出现依赖冲突。而Maven会对这些冲突进行调解,以确定最终使用的依赖版本。
Maven依赖调解(Dependency Mediation)的两个原则:
第一原则:路径最短优先:Maven首先选择路径最短的依赖路径。路径的长度是指从项目根目录到依赖库的路径长度。
项目 -> A -> D -> C(版本2.1)
-> B -> C(版本2.0) ✅
第二原则:第一声明者优先:如果依赖路径都是一样长,依赖声明的顺序决定了谁会被使用,即顺序最高的依赖会被引入。
项目 -> A -> C(版本2.1) ✅
-> B -> C(版本2.0)
6.可选依赖
在Maven中,可以通过<optional>
元素来标记某个依赖为可选的。这意味着该依赖不是项目必须的,而是项目可以选择性地使用的。
应用案例: 在一些项目中,我们可能希望建立一个核心库,并提供一系列可选的扩展模块。例如spring-cloud-commons模块中,提供了一些公共的组件和功能,用于构建基于微服务的分布式系统。
<!-- 面向切面编程(AOP)功能 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.4.1</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<!-- 自动配置类注解处理器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<version>2.4.1</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
当我们在项目中引入spring-cloud-commons依赖时,以上可选依赖不会被包含在项目中,只有在项目pom.xml中显示声明以上依赖,他才会被引入。
7.排除依赖
在Maven中,可以使用<exclusions>
元素来排除依赖项的传递性依赖。
当一个项目依赖于某个依赖(A),而依赖A又依赖于其他依赖(B、C等),当你在项目中声明A作为依赖时,默认情况下,Maven会自动解析和引入A所依赖的其他依赖。然而,有时可能希望排除传递依赖项中的特定依赖项,这时你可以使用<exclusions>
元素来实现。它允许你指定要排除的依赖项的<groupId>
和<artifactId>
。
例如,假设你的项目依赖于A,而A又依赖于B和C。但是你希望排除依赖项C,你可以使用以下方式:
<dependency>
<groupId>com.example</groupId>
<artifactId>A</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<groupId>com.example</groupId>
<artifactId>C</artifactId>
</exclusion>
</exclusions>
</dependency>
注:内容源于《Maven实战》(许晓斌著)