41-Maven 包依赖
简介
Maven 中有两种依赖类型:直接(依赖)和 传递(依赖)
直接依赖项是我们明确包含在项目中的那些(依赖)。这些可以使用
<dependency>
标签包含:1
2
3
4
5<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
</dependency>直接依赖的包需要依赖其他包,这个就叫做传递依赖。 Maven 会自动在我们的项目中包含所需的传递依赖项。
比如我们的项目依赖
spring-webmv.jar
,而spring-webmv.jar
会依赖spring-beans.jar
等等,所以spring-beans.jar
这些 jar 包也出现在了我们的 maven 工程中,这种现象我们称为依赖传递。有了传递性依赖机制,在使用 Spring Framework 的时候就不用去考虑它依赖了什么,也不用担心引入多余的依赖。Maven 会解析各个直接依赖的 POM,将那些必要的间接依赖,以传递性依赖的形式引入到当前的项目中。
我们可以使用
mvn dependency:tree
命令列出项目中的所有依赖项,包括传递依赖项。
依赖范围
依赖范围就是用来控制依赖和三种 classpath(编译classpath,测试classpath、运行classpath)的关系,帮助限制依赖的传递性,Maven有 六个 默认的依赖范围:
compile: 编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的Maven依赖,对于编译、测试、运行三种classpath都有效。典型的例子是spring-code,在编译、测试和运行的时候都需要使用该依赖。
test: 测试依赖范围。使用次依赖范围的Maven依赖,只对于测试classpath有效,在编译主代码或者运行项目的使用时将无法使用此依赖。典型的例子是Jnuit,它只有在编译测试代码及运行测试的时候才需要。
provided: 已提供依赖范围。使用此依赖范围的Maven依赖,对于编译和测试classpath有效,但在运行时候无效。典型的例子是servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器以及提供,就不需要Maven重复地引入一遍。
runtime: 运行时依赖范围。使用此依赖范围的Maven依赖,对于测试和运行classpath有效,但在编译主代码时无效。典型的例子是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。
system: 系统依赖范围。该依赖与三种classpath的关系,和provided依赖范围完全一致,但是,使用system范围的依赖时必须通过systemPath元素显示地指定依赖文件的路径。由于此类依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能构成构建的不可移植,因此应该谨慎使用。systemPath元素可以引用环境变量,如:
1
2
3
4
5
6
7<dependency>
<groupId>javax.sql</groupId>
<artifactId>jdbc-stdext</artifactId>
<Version>2.0</Version>
<scope>system</scope>
<systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>import: 导入依赖范围。该依赖范围不会对三种classpath产生实际的影响。
上述除import以外的各种依赖范围与三种classpath的关系如下:
依赖范围 | 编译 | 测试 | 运行时 | 是否被打包 |
---|---|---|---|---|
compile | √ | √ | √ | √ |
provided | √ | √ | × | × |
runtime | × | √ | √ | √ |
test | × | √ | × | × |
system | √ | √ | × | √ |
依赖调解
有时候,当多个包有相同的包依赖,但是包的版本却不同,这个时候就需要清楚地知道该传递性依赖是从哪条依赖路径引入的。这就是依赖调解的作用,依赖调解有两大原则:
路径最近者优先。比如项目有A有这样的依赖关系:A->B->C->X(1.0)、A->D->X(2.0),X是A的传递性依赖,但是两条依赖路径上有两个版本的X,所以根据第一原则,A->D->X(2.0)路径短,所以X(2.0)会被解析使用
第一声明者优先。如果路径都一样长的话,第一原则就不行了,比如 A->B->Y(1.0)、A->C->Y(2.0),Y(1.0)和Y(2.0)的路径一样,所以这时候根据第二原则,先声明的被解析。
可选依赖 option
可选依赖是通过项目中的 POM 文件的依赖元素 dependency 下的option元素中进行配置,只有显式地配置项目中某依赖的option元素为true时,该依赖才是可选依赖;不设置该元素或值为false时,该依赖即不是可选依赖。其意义在于,当某个间接依赖是可选依赖时,无论依赖范围是什么,其都不会因为传递性依赖机制而被引入。
假设在项目A中存在如下依赖关系
如图,项目中A依赖B,B依赖于X和Y,如果所有这三个的范围都是compile的话,那么X和Y就是A的compile范围的传递性依赖,但是如果我想X,Y不作为A的传递性依赖,就需要下面的配置可选依赖。
1 |
|
排除依赖 exclusions
有时候你引入的依赖中包含你不想要的依赖包(比如由于版本(1.0)过低存在安全漏洞),你想引入自己想要的,这时候就要用到排除依赖了。
比如下图中spring-boot-starter-web自带了logback这个日志包,我想引入log4j2的,所以我先排除掉logback的依赖包,再引入想要的包就行了
排除依赖代码结构:
1 |
|
这里注意:声明exclustion的时候只需要groupId和artifactId,而不需要version元素,这是因为只需要groupId和artifactId就能唯一定位依赖图中的某个依赖。
锁定依赖(常用)
面对众多的依赖,有一种方法不用考虑依赖路径、声明优化等因素可以采用直接锁定版本的方法确定依赖构件的版本,版本锁定后则不考虑依赖的声明顺序或依赖的路径,以锁定的版本的为准添加到工程中,此方法在企业开发中常用。
1 |
|
注意:在工程中锁定依赖的版本并不代表在工程中添加了依赖,如果工程需要添加锁定版本的依赖则需要单独添加<dependencies></dependencies>
标签,而且此时导入被锁定的包,不需要写版本号。
归类依赖
有时候我们引入的很多依赖包,他们都来自同一个项目的不同模块,所以他们的版本号都一样,这时候我们可以用属性来统一管理版本号
1 |
|
依赖的常用命令
查看引入的依赖列表。
通过下述命令,可以查看当前项目中所有引入的依赖、版本、依赖范围信息列表,包含直接依赖和通过传递依赖引入的间接依赖
1
mvn dependency:list
查看引入的依赖树。
通过下述命令,可以查看当前项目中所有引入的依赖、版本、依赖范围信息。其结果以树形结构图的形式展示,第一级的依赖(红框部分)即为直接依赖,剩下的(绿框部分)则为通过传递依赖引入的间接依赖
1
mvn dependency:tree
依赖分析。
通过下述命令,可以分析当前项目中依赖的使用情况
1
mvn dependency:analyze
该分析报告中有两个部分需要我们重点关注
- Used undeclared dependencies found : 指项目中使用到的但未显式声明的依赖,即在项目中的代码使用了通过传递引入的依赖。如上图绿框所示
- Unused declared dependencies found : 指项目中未使用的但显式声明的依赖,如上图红框所示。由于该命令实际上是分析编译阶段中所使用的依赖,而对于测试、运行阶段依赖的使用情况则无法分析,所以,这里列出的未使用的依赖,可能是冗余的依赖,但也可能是测试、运行阶段所必须的依赖。故应谨慎删除