bboyjing's blog

Maven学习笔记七【可选的依赖项和依赖项排除】

介绍

本节可选的依赖项和排除依赖项。将帮助我们了解它们是什么,合适以及如何使用。它还解释了为什么排除是基于每个依赖项而不是POM级别。

可选依赖项

可选的依赖项用于无法(无论出于何种原因)将项目分割为子模块的情况。其思想是,一些依赖项仅用于项目中的某些特性,如果不使用该特性,则不需要这些依赖项。理想情况下,这样的特性应该被分割成依赖于核心功能项目的子模块。这个新的子项目将只有非可选的依赖项,因为如果决定使用子项目的功能,将需要所有这些依赖项。

然而,由于项目不能分割(同样,出于某种原因),这些依赖项声明为可选的。如果用户希望使用与可选依赖项相关的功能,他们必须在自己的项目中重新声明该可选依赖项。这不是处理这种情况的最清晰的方法,但是可选的依赖项和排除依赖项都是权宜之计。

为什么使用可选依赖项

可选的依赖项可以节省空间和内存。它们可以防止违反许可协议或导致类路径问题的jar被捆绑到WAR、EAR、fat jar或类似的文件中。

如何使用可选标记

通过在依赖项声明中将<optional>元素设置为true,可以将依赖项声明为optional:

1
2
3
4
5
6
7
8
9
10
11
12
13
<project>
...
<dependencies>
<!-- declare the dependency to be set as optional -->
<dependency>
<groupId>sample.ProjectA</groupId>
<artifactId>Project-A</artifactId>
<version>1.0</version>
<scope>compile</scope>
<optional>true</optional> <!-- value will be true or false only -->
</dependency>
</dependencies>
</project>

可选依赖项如何工作

1
Project-A -> Project-B

上面的图表表示项目A依赖于项目B。当A在其POM中将B声明为可选依赖项时,此关系保持不变。它就像一个普通的构建,项目B将被添加到项目A的类路径中

1
Project-X -> Project-A

当另一个项目(Project-X)在其POM中将Project-A声明为依赖项时,依赖项的可选性质就会生效。Project-B不包含在Project-X的类路径中。您需要在项目X的POM中直接声明它,以便将B包含在X的类路径中。

例子

假设有一个名为X2的项目具有与Hibernate类似的功能。它支持许多数据库,如MySQL、PostgreSQL和Oracle的几个版本。每个支持的数据库都需要额外的驱动jar包依赖。在编译时构建X2需要所有这些依赖项。但是,我们的数据库只使用一个特定的数据库,不需要为其它数据库提供驱动。X2可以将这些依赖项声明为可选的,这样当项目在其POM中将X2声明为直接依赖项时,X2支持的所有驱动程序不会自动包含在项目的类路径中。项目必须为它所使用的数据库包含对特定驱动程序的显式依赖。

依赖项排除

由于Maven是临时解决依赖项的,所以不需要的依赖项可能包含在项目的类路径中。例如,某个较旧的jar可能存在安全问题,或者与我们正在使用的Java版本不兼容。为了解决这个问题,Maven允许我们排除特定的依赖项。排除是在POM中的特定依赖项上设置的,并且针对特定的groupId和artifactId。在构建项目时,不会将声明了排除依赖关系的项目添加到类路径中。

如何使用依赖排除

在包含有问题的jar的<dependency>元素中添加< exclude >元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<project>
...
<dependencies>
<dependency>
<groupId>sample.ProjectA</groupId>
<artifactId>Project-A</artifactId>
<version>1.0</version>
<scope>compile</scope>
<exclusions>
<exclusion> <!-- declare the exclusion here -->
<groupId>sample.ProjectB</groupId>
<artifactId>Project-B</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

依赖排除是如何工作的以及什么时候使用它

1
2
3
4
5
6
Project-A
-> Project-B
-> Project-D <! -- This dependency should be excluded -->
-> Project-E
-> Project-F
-> Project C

该图显示项目A同时依赖于项目B和项目C。项目B依赖于项目D。项目D同时依赖于项目E和F。默认情况下,项目A的类路径包括:

1
B, C, D, E, F

假设我们不希望将项目D及其依赖项添加到项目A的类路径中,因为存储库中缺少一些项目D的依赖项,而且也不需要项目B中依赖于项目D的功能。项目B的开发人员可以标记对项目D设置<optional>true</optional>依赖关系:

1
2
3
4
5
6
<dependency>
<groupId>sample.ProjectD</groupId>
<artifactId>ProjectD</artifactId>
<version>1.0-SNAPSHOT</version>
<optional>true</optional>
</dependency>

不幸的是,他们没有。最后,我们可以将其排除在您自己的项目POM中,如下所示:

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>sample.ProjectA</groupId>
<artifactId>Project-A</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
...
<dependencies>
<dependency>
<groupId>sample.ProjectB</groupId>
<artifactId>Project-B</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>sample.ProjectD</groupId> <!-- Exclude Project-D from Project-B -->
<artifactId>Project-D</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

如果将Project-A部署到存储库中,并且Project-X声明了对Project-A的正常依赖关系,那么Project-D仍然会被排除在类路径之外吗?

1
Project-X -> Project-A

答案是肯定的。Project-A已经声明它不需要Project-D来运行,所以它不会作为Project-A的传递依赖项被引入。

现在,考虑项目X依赖于项目Y,如下图所示:

1
2
3
4
Project-X -> Project-Y
-> Project-B
-> Project-D
...

Project-Y也依赖Project-B,它确实需要Project-D支持的特性。因此,它不会在依赖项列表中排除Project-D。它还可以提供一个额外的存储库,用于解析Project-E。在这种情况下,重要的是Project-D没有被全局排除,因为它是Project-Y的合法依赖项。

作为另一种场景,假设您不想要的依赖项是Project-E而不是Project-D。如何排除它?如下图所示:

1
2
3
4
5
6
Project-A
-> Project-B
-> Project-D
-> Project-E <!-- Exclude this dependency -->
-> Project-F
-> Project C

如果您想要排除Project-E而不是Project-D,只需将排除更改为指向Project-E,但不会将排除移动到Project-D。您不能更改Project-D的POM。如果可以,您可以使用可选的依赖项而不是排除项,或者将Project-D分割成多个子项目,每个子项目只有正常的依赖项。

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>sample.ProjectA</groupId>
<artifactId>Project-A</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
...
<dependencies>
<dependency>
<groupId>sample.ProjectB</groupId>
<artifactId>Project-B</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>sample.ProjectE</groupId> <!-- Exclude Project-E from Project-B -->
<artifactId>Project-E</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

为什么排除是基于每个依赖项而不是POM级别

这主要是为了确保依赖关系图是可预测的,并防止继承效果排除不应该排除的依赖关系。如果使用最后一种方法,并且必须放入一个排除,那么应该完全确定是哪个依赖项引入了不需要的传递依赖项。

如果我们确实希望确保某个特定的依赖项在类路径中不出现,无论路径如何,都可以将禁用的依赖项规则(bannedDependencies)配置为在发现有问题的依赖项时构建失败。当构建失败时,您需要在执行者找到的每个路径上添加特定的排除。