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的关系如下:

image.png

依赖范围 编译 测试 运行时 是否被打包
compile
provided × ×
runtime ×
test × × ×
system ×

依赖调解

有时候,当多个包有相同的包依赖,但是包的版本却不同,这个时候就需要清楚地知道该传递性依赖是从哪条依赖路径引入的。这就是依赖调解的作用,依赖调解有两大原则:

  1. 路径最近者优先。比如项目有A有这样的依赖关系:A->B->C->X(1.0)、A->D->X(2.0),X是A的传递性依赖,但是两条依赖路径上有两个版本的X,所以根据第一原则,A->D->X(2.0)路径短,所以X(2.0)会被解析使用

  2. 第一声明者优先。如果路径都一样长的话,第一原则就不行了,比如 A->B->Y(1.0)、A->C->Y(2.0),Y(1.0)和Y(2.0)的路径一样,所以这时候根据第二原则,先声明的被解析。

可选依赖 option

可选依赖是通过项目中的 POM 文件的依赖元素 dependency 下的option元素中进行配置,只有显式地配置项目中某依赖的option元素为true时,该依赖才是可选依赖;不设置该元素或值为false时,该依赖即不是可选依赖。其意义在于,当某个间接依赖是可选依赖时,无论依赖范围是什么,其都不会因为传递性依赖机制而被引入。

假设在项目A中存在如下依赖关系

image.png

如图,项目中A依赖B,B依赖于X和Y,如果所有这三个的范围都是compile的话,那么X和Y就是A的compile范围的传递性依赖,但是如果我想X,Y不作为A的传递性依赖,就需要下面的配置可选依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<project>  
<modelVersion>4.0.0</modelVersion>
<groupId>com.juvenxu.mvnbook</groupId>
<artifactId>project-b</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.10</version>
<optional>true</optional> <!-- 在这里在这里配置-->
</dependency>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</groupId>
<version>8.4-701.jdbc3</version>
<optional>true</optional> <!-- 在这里在这里配置-->
</dependency>
</dependencies>
</project>

排除依赖 exclusions

有时候你引入的依赖中包含你不想要的依赖包(比如由于版本(1.0)过低存在安全漏洞),你想引入自己想要的,这时候就要用到排除依赖了。

比如下图中spring-boot-starter-web自带了logback这个日志包,我想引入log4j2的,所以我先排除掉logback的依赖包,再引入想要的包就行了

image.png

排除依赖代码结构:

1
2
3
4
5
6
<exclusions>
<exclusion> <!-- 在这里在这里配置-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>

这里注意:声明exclustion的时候只需要groupId和artifactId,而不需要version元素,这是因为只需要groupId和artifactId就能唯一定位依赖图中的某个依赖。

锁定依赖(常用)

面对众多的依赖,有一种方法不用考虑依赖路径、声明优化等因素可以采用直接锁定版本的方法确定依赖构件的版本,版本锁定后则不考虑依赖的声明顺序或依赖的路径,以锁定的版本的为准添加到工程中,此方法在企业开发中常用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--spring的jdbc包 此时导入被锁定的包,不需要写版本号,若写了就以就近原则下载 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
</dependencies>

注意:在工程中锁定依赖的版本并不代表在工程中添加了依赖,如果工程需要添加锁定版本的依赖则需要单独添加<dependencies></dependencies>标签,而且此时导入被锁定的包,不需要写版本号。

归类依赖

有时候我们引入的很多依赖包,他们都来自同一个项目的不同模块,所以他们的版本号都一样,这时候我们可以用属性来统一管理版本号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<project>  
<modelVersion>4.0.0</modelVersion>
<groupId>com.juven.mvnbook.account</groupId>
<artifactId>accout-email</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<springframework.version>1.5.6</springframework.version> <!-- 在这里在这里配置-->
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${springframework.version}</version> <!-- 在这里在这里配置-->
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${springframework.version}</version> <!-- 在这里在这里配置-->
</dependency>
</dependencies>
</project>

依赖的常用命令

  1. 查看引入的依赖列表。

    通过下述命令,可以查看当前项目中所有引入的依赖、版本、依赖范围信息列表,包含直接依赖和通过传递依赖引入的间接依赖

    1
    mvn dependency:list
  2. 查看引入的依赖树。

    通过下述命令,可以查看当前项目中所有引入的依赖、版本、依赖范围信息。其结果以树形结构图的形式展示,第一级的依赖(红框部分)即为直接依赖,剩下的(绿框部分)则为通过传递依赖引入的间接依赖

    1
    mvn dependency:tree
  3. 依赖分析。

    通过下述命令,可以分析当前项目中依赖的使用情况

    1
    mvn dependency:analyze

    该分析报告中有两个部分需要我们重点关注

    • Used undeclared dependencies found : 指项目中使用到的但未显式声明的依赖,即在项目中的代码使用了通过传递引入的依赖。如上图绿框所示
    • Unused declared dependencies found : 指项目中未使用的但显式声明的依赖,如上图红框所示。由于该命令实际上是分析编译阶段中所使用的依赖,而对于测试、运行阶段依赖的使用情况则无法分析,所以,这里列出的未使用的依赖,可能是冗余的依赖,但也可能是测试、运行阶段所必须的依赖。故应谨慎删除

41-Maven 包依赖
https://flepeng.github.io/021-Java-13-Maven-41-Maven-包依赖/
作者
Lepeng
发布于
2021年4月22日
许可协议