介绍

本节讨论可选依赖项和依赖项排除。这将帮助用户了解它们是什么以及何时以及如何使用它们。它还解释了为什么排除是基于每个依赖项而不是在 POM 级别进行的。

可选依赖项

当无法(无论出于何种原因)将项目拆分为子模块时,将使用可选依赖项。这个想法是,某些依赖项仅用于项目中的某些功能,如果不使用该功能,则不需要。理想情况下,此类功能将被拆分为依赖于核心功能项目的子模块。这个新的子项目将只有非可选的依赖项,因为如果您决定使用子项目的功能,您将需要它们。

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

为什么使用可选依赖项?

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

如何使用可选标签?

通过在其依赖项声明中将 <optional> 元素设置为 true 来声明依赖项是可选的:

<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>

可选依赖项如何工作?

Project-A -> Project-B

上图表示 Project-A 依赖于 Project-B。当 A 在其 POM 中将 B 声明为可选依赖项时,这种关系保持不变。这就像一个正常的构建,将 Project-B 添加到 Project-A 的类路径中。

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> 元素中添加 <exclusions> 元素。

<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>

依赖排除如何工作以及何时使用它(作为最后的手段!)

Project-A
   -> Project-B
        -> Project-D <! -- This dependency should be excluded -->
              -> Project-E
              -> Project-F
   -> Project C

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

B, C, D, E, F

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

<dependency>
  <groupId>sample.ProjectD</groupId>
  <artifactId>ProjectD</artifactId>
  <version>1.0-SNAPSHOT</version>
  <optional>true</optional>
</dependency>

不幸的是,他们没有。作为最后的手段,您可以在自己的 POM 中为 Project-A 排除它,如下所示:

<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 是否仍会从类路径中排除?

Project-X -> Project-A

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

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

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。你如何排除它?见下图:

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 拆分为多个子项目,每个子项目都只有正常的依赖项。

<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 级别

这主要是为了确保依赖关系图是可预测的,并防止继承影响排除不应该排除的依赖关系。如果您使用最后的方法并且必须排除,您应该绝对确定您的哪个依赖项带来了不需要的传递依赖项。

如果您确实想确保特定依赖项不会出现在类路径中,无论路径如何,都可以将禁用依赖项规则配置为在发现有问题的依赖项时使构建失败。当构建失败时,您需要在执行器找到的每条路径上添加特定的排除项。