Maven Dependency 机制

依赖关系管理是Maven的核心功能。管理单个项目的依赖关系很容易。管理由数百个模块组成的多模块项目和应用程序的依赖关系是可能的。Maven在定义、创建和维护具有良好定义的类路径和库版本的可复制构建方面有很大帮助。

一、传递依赖

Maven通过自动包含可传递的依赖关系,避免了发现和指定您自己的依赖关系所需的库的需要。
通过从指定的远程存储库中读取依赖项的项目文件,可以实现此功能。通常,这些项目的所有依赖项都会在项目中使用,项目从其父项或依赖项继承的任何依赖项也是如此,依此类推。
可以从中收集依赖关系的级别数量没有限制。只有当发现循环依赖关系时,才会出现问题。
有了可传递的依赖关系,包含库的图可以很快变大。因此,还有一些附加功能限制了所包含的依赖项:

  • 依赖项中介 - 这决定了在遇到多个版本作为依赖项时将选择哪个版本的项目。Maven选择了“最接近的定义”。也就是说,它使用依赖项树中与项目最近的依赖项的版本。您始终可以通过在项目的 POM 中显式声明版本来保证版本。请注意,如果两个依赖项版本在依赖项树中的深度相同,则第一个声明优先。
    • “最接近的定义”意味着使用的版本将是依赖项树中与您的项目最接近的版本。考虑以下依赖树:
 A
  ├── B
  │   └── C
  │       └── D 2.0
  └── E
      └── D 1.0

 在文本中,A、B和C的依赖项定义为A->B->C->D2.0和A->E->D1.0,然后在构建A时将使用D1.0,因为从A到D通过E的路径更短。您可以在a中显式地向D2.0添加依赖项,以强制使用D2.0,如下所示:

  A
  ├── B
  │   └── C
  │       └── D 2.0
  ├── E
  │   └── D 1.0
  │
  └── D 2.0    
  • 依赖项管理-这允许项目作者在传递依赖项或未指定版本的依赖项中遇到工件时,直接指定要使用的工件版本。在上一节的例子中,一个依赖项被直接添加到a中,尽管a没有直接使用它。相反,a可以将D作为依赖项包含在其dependencyManagement部分中,并直接控制在何时或是否引用它时使用D的哪个版本。
  • 依赖关系范围-这允许您只包括适用于构建的当前阶段的依赖关系。下面将对此进行更详细的描述。
  • 排除的依赖项-如果项目X依赖于项目Y,而项目Y依赖于项目Z,则项目X的所有者可以使用“排除”元素将项目Z明确排除为依赖项。
  • 可选依赖项-如果项目Y依赖于项目Z,则项目Y的所有者可以使用“可选”元素将项目Z标记为可选依赖项。当项目X依赖于项目Y时,X将仅依赖于Y,而不依赖于Y的可选依赖项Z。然后,项目X的所有者可以根据自己的选择显式添加对Z的依赖项。(将可选依赖项视为“默认排除”可能会有所帮助)

尽管可传递依赖项可以隐式地包括所需的依赖项,但显式指定源代码直接使用的依赖项是一种很好的做法。这种最佳实践证明了它的价值,尤其是当项目的依赖关系改变了它们的依赖关系时。
例如,假设您的项目A指定了对另一个项目B的依赖项,而项目B指定了对项目C的依赖项。如果您直接在项目C中使用组件,而您没有在项目A中指定项目C,则当项目B突然更新/删除其对项目C的依赖时,可能会导致生成失败。
直接指定依赖项的另一个原因是它为您的项目提供了更好的文档:只需读取项目中的POM文件,或者执行mvn-dependency:tree,就可以了解更多信息。
Maven还提供了dependency:analyze插件分析依赖关系的目标:这有助于使这种最佳实践更容易实现。

二、依赖范围

依赖关系范围用于限制依赖关系的传递性,并确定依赖关系何时包含在类路径中。
共有6个作用域:

  • compile

     这是默认范围,如果未指定,则使用。编译依赖项在项目的所有类路径中都可用。此外,这些依赖项将传播到依赖项目。

  • provided

  这很像编译,但表示您希望JDK或容器在运行时提供依赖关系。例如,当为Java Enterprise Edition构建web应用程序时,您需要将对Servlet API和相关Java EE API的依赖设置为所提供的范围,因为web容器提供了这些类。具有此作用域的依赖项被添加到用于编译和测试的类路径中,但不添加到运行时类路径中。它不可传递。

  • runtime

    此范围表示编译不需要依赖项,而是执行依赖项。Maven在运行时和测试类路径中包含一个具有此作用域的依赖项,但不包括编译类路径。

  • test

此范围表示应用程序的正常使用不需要依赖项,仅适用于测试编译和执行阶段。此范围不可传递。通常,这个范围用于JUnit和Mockito等测试库。它也用于非测试库,如Apache Commons IO,如果这些库用于单元测试(src/test/java),但不用于模型代码(src/main/java)。

  • system

   这个作用域与提供的作用域类似,只是您必须提供显式包含它的JAR。工件总是可用的,并且不会在存储库中查找。

  • import

      只有<dependencyManagement>部分中pom类型的依赖项才支持此作用域。它指示依赖项将被指定POM的<dependencyManagement>部分中的有效依赖项列表所取代。由于它们被替换,具有导入范围的依赖项实际上并没有参与限制依赖项的传递性。

每个作用域(导入除外)都以不同的方式影响可传递的依赖项,如下表所示。如果将依赖项设置为左列中的作用域,则该依赖项与顶行中作用域的可传递依赖项将导致主项目中的依赖项与交叉点处列出的作用域。如果没有列出作用域,则表示省略了依赖项。

compile

provided

runtime

test

compile

compile(*)

-

runtime

-

provided

provided

-

provided

-

runtime

runtime

-

runtime

-

test

test

-

test

-

(*)注意:这应该是运行时范围,因此必须显式列出所有编译依赖项。但是,如果您所依赖的库从另一个库扩展了一个类,则两者在编译时都必须可用。由于这个原因,编译时依赖关系仍然是编译范围,即使它们是可传递的。

三、依赖关系管理

依赖关系管理部分是一种用于集中依赖关系信息的机制。当您有一组从公共父级继承的项目时,可以将有关依赖项的所有信息放在公共 POM 中,并对子 POM 中的项目进行更简单的引用。通过一些例子可以最好地说明这种机制。给定扩展同一父级的这两个 POM:

项目A:

<project>
  ...
  <dependencies>
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-a</artifactId>
      <version>1.0</version>
      <exclusions>
        <exclusion>
          <groupId>group-c</groupId>
          <artifactId>excluded-artifact</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-b</artifactId>
      <version>1.0</version>
      <type>bar</type>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
</project>

项目B:

<project>
  ...
  <dependencies>
    <dependency>
      <groupId>group-c</groupId>
      <artifactId>artifact-b</artifactId>
      <version>1.0</version>
      <type>war</type>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-b</artifactId>
      <version>1.0</version>
      <type>bar</type>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
</project>

这两个示例 POM 共享一个共同的依赖项,并且每个都有一个非平凡的依赖项。此信息可以像这样放入父 POM 中:

 
<project>
  ...
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>group-a</groupId>
        <artifactId>artifact-a</artifactId>
        <version>1.0</version>
 
        <exclusions>
          <exclusion>
            <groupId>group-c</groupId>
            <artifactId>excluded-artifact</artifactId>
          </exclusion>
        </exclusions>
 
      </dependency>
 
      <dependency>
        <groupId>group-c</groupId>
        <artifactId>artifact-b</artifactId>
        <version>1.0</version>
        <type>war</type>
        <scope>runtime</scope>
      </dependency>
 
      <dependency>
        <groupId>group-a</groupId>
        <artifactId>artifact-b</artifactId>
        <version>1.0</version>
        <type>bar</type>
        <scope>runtime</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>

然后,两个子 POM 变得更加简单:

<project>
  ...
  <dependencies>
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-a</artifactId>
    </dependency>
 
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-b</artifactId>
      <!-- This is not a jar dependency, so we must specify type. -->
      <type>bar</type>
    </dependency>
  </dependencies>
</project>
<project>
  ...
  <dependencies>
    <dependency>
      <groupId>group-c</groupId>
      <artifactId>artifact-b</artifactId>
      <!-- This is not a jar dependency, so we must specify type. -->
      <type>war</type>
    </dependency>
 
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-b</artifactId>
      <!-- This is not a jar dependency, so we must specify type. -->
      <type>bar</type>
    </dependency>
  </dependencies>
</project>

 注意:在其中两个依赖引用中,我们必须指定<type/>元素。这是因为用于将依赖引用与dependencyManagement节匹配的最小信息集实际上是{groupId,artifactId,type,classifier}。在许多情况下,这些依赖关系将引用没有分类器的jar工件。这允许我们将标识集简写为{groupId,artifactId},因为类型字段的默认值是jar,而默认分类器是null。
依赖关系管理部分的第二个也是非常重要的用途是控制可传递依赖关系中使用的工件的版本。以这些项目为例:

项目A:

<project>
 <modelVersion>4.0.0</modelVersion>
 <groupId>maven</groupId>
 <artifactId>A</artifactId>
 <packaging>pom</packaging>
 <name>A</name>
 <version>1.0</version>
 <dependencyManagement>
   <dependencies>
     <dependency>
       <groupId>test</groupId>
       <artifactId>a</artifactId>
       <version>1.2</version>
     </dependency>
     <dependency>
       <groupId>test</groupId>
       <artifactId>b</artifactId>
       <version>1.0</version>
       <scope>compile</scope>
     </dependency>
     <dependency>
       <groupId>test</groupId>
       <artifactId>c</artifactId>
       <version>1.0</version>
       <scope>compile</scope>
     </dependency>
     <dependency>
       <groupId>test</groupId>
       <artifactId>d</artifactId>
       <version>1.2</version>
     </dependency>
   </dependencies>
 </dependencyManagement>
</project>

项目B:

<project>
  <parent>
    <artifactId>A</artifactId>
    <groupId>maven</groupId>
    <version>1.0</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>
  <groupId>maven</groupId>
  <artifactId>B</artifactId>
  <packaging>pom</packaging>
  <name>B</name>
  <version>1.0</version>
 
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>test</groupId>
        <artifactId>d</artifactId>
        <version>1.0</version>
      </dependency>
    </dependencies>
  </dependencyManagement>
 
  <dependencies>
    <dependency>
      <groupId>test</groupId>
      <artifactId>a</artifactId>
      <version>1.0</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>test</groupId>
      <artifactId>c</artifactId>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
</project>

 当maven在项目B上运行时,无论POM中指定的版本是什么,都将使用工件a、B、c和d的1.0版本。

  • a和c都被声明为项目的依赖项,因此由于依赖项中介,使用1.0版本。两者都有运行时作用域,因为它是直接指定的。
  • b是在b的父级依赖关系管理部分中定义的,由于对于可传递依赖关系,依赖关系管理优先于依赖关系中介,因此如果在a或c的POM中引用它,将选择1.0版本。b也将具有编译范围。
  • 最后,由于d是在B的依赖关系管理部分中指定的,如果d是a或c的依赖关系(或传递依赖关系),则将选择1.0版本,这也是因为依赖关系管理优先于依赖关系中介,也因为当前POM的声明优先于其父声明。

有关依赖项管理标记的引用信息可从项目描述符引用中获得。

四、正在导入依赖项

上一节中的示例描述了如何通过继承指定托管依赖项。然而,在较大的项目中,这可能是不可能实现的,因为一个项目只能从单亲继承。为了适应这种情况,项目可以从其他项目导入托管依赖项。这是通过将POM工件声明为范围为“import”的依赖项来实现的。

Project B:

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>maven</groupId>
  <artifactId>B</artifactId>
  <packaging>pom</packaging>
  <name>B</name>
  <version>1.0</version>
 
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>maven</groupId>
        <artifactId>A</artifactId>
        <version>1.0</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>test</groupId>
        <artifactId>d</artifactId>
        <version>1.0</version>
      </dependency>
    </dependencies>
  </dependencyManagement>
 
  <dependencies>
    <dependency>
      <groupId>test</groupId>
      <artifactId>a</artifactId>
      <version>1.0</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>test</groupId>
      <artifactId>c</artifactId>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
</project>

假设A是前面例子中定义的POM,那么最终结果将是相同的。A的所有托管依赖项都将被合并到B中,除了d,因为它是在这个POM中定义的。

Project X:

<project>
 <modelVersion>4.0.0</modelVersion>
 <groupId>maven</groupId>
 <artifactId>X</artifactId>
 <packaging>pom</packaging>
 <name>X</name>
 <version>1.0</version>
 
 <dependencyManagement>
   <dependencies>
     <dependency>
       <groupId>test</groupId>
       <artifactId>a</artifactId>
       <version>1.1</version>
     </dependency>
     <dependency>
       <groupId>test</groupId>
       <artifactId>b</artifactId>
       <version>1.0</version>
       <scope>compile</scope>
     </dependency>
   </dependencies>
 </dependencyManagement>
</project>

Project Y: 

<project>
 <modelVersion>4.0.0</modelVersion>
 <groupId>maven</groupId>
 <artifactId>Y</artifactId>
 <packaging>pom</packaging>
 <name>Y</name>
 <version>1.0</version>
 
 <dependencyManagement>
   <dependencies>
     <dependency>
       <groupId>test</groupId>
       <artifactId>a</artifactId>
       <version>1.2</version>
     </dependency>
     <dependency>
       <groupId>test</groupId>
       <artifactId>c</artifactId>
       <version>1.0</version>
       <scope>compile</scope>
     </dependency>
   </dependencies>
 </dependencyManagement>
</project>

Project Z: 

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>maven</groupId>
  <artifactId>Z</artifactId>
  <packaging>pom</packaging>
  <name>Z</name>
  <version>1.0</version>
 
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>maven</groupId>
        <artifactId>X</artifactId>
        <version>1.0</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>maven</groupId>
        <artifactId>Y</artifactId>
        <version>1.0</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>

在上面的示例中,Z从X和Y导入托管依赖项。然而,X和Y都包含依赖项a。这里,将使用a的1.1版本,因为X是首先声明的,而a不是在Z的dependencyManagement中声明的。
这个过程是递归的。例如,如果X导入另一个POM Q,那么当Z被处理时,它将简单地显示出Q的所有托管依赖项都是在X中定义的。

五、BOM表POM

当用于定义通常是多项目构建的一部分的相关工件的“库”时,导入最有效。一个项目使用这些库中的一个或多个工件是相当常见的。然而,有时很难使用工件来保持项目中的版本与库中分发的版本同步。下面的模式说明了如何创建“BOM表”(BOM)以供其他项目使用。
项目的根是BOM POM。它定义了将在库中创建的所有工件的版本。其他希望使用库的项目应该将此POM导入其POM的dependencyManagement部分。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.test</groupId>
  <artifactId>bom</artifactId>
  <version>1.0.0</version>
  <packaging>pom</packaging>
  <properties>
    <project1Version>1.0.0</project1Version>
    <project2Version>1.0.0</project2Version>
  </properties>
 
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.test</groupId>
        <artifactId>project1</artifactId>
        <version>${project1Version}</version>
      </dependency>
      <dependency>
        <groupId>com.test</groupId>
        <artifactId>project2</artifactId>
        <version>${project2Version}</version>
      </dependency>
    </dependencies>
  </dependencyManagement>
 
  <modules>
    <module>parent</module>
  </modules>
</project>

父子项目将BOM POM作为其父项目。这是一个正常的多项目pom。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.test</groupId>
    <version>1.0.0</version>
    <artifactId>bom</artifactId>
  </parent>
 
  <groupId>com.test</groupId>
  <artifactId>parent</artifactId>
  <version>1.0.0</version>
  <packaging>pom</packaging>
 
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.12</version>
      </dependency>
      <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.1.1</version>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <modules>
    <module>project1</module>
    <module>project2</module>
  </modules>
</project>

 接下来是实际的项目POM:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.test</groupId>
    <version>1.0.0</version>
    <artifactId>parent</artifactId>
  </parent>
  <groupId>com.test</groupId>
  <artifactId>project1</artifactId>
  <version>${project1Version}</version>
  <packaging>jar</packaging>
 
  <dependencies>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
    </dependency>
  </dependencies>
</project>
 
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.test</groupId>
    <version>1.0.0</version>
    <artifactId>parent</artifactId>
  </parent>
  <groupId>com.test</groupId>
  <artifactId>project2</artifactId>
  <version>${project2Version}</version>
  <packaging>jar</packaging>
 
  <dependencies>
    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
    </dependency>
  </dependencies>
</project>

下面的项目显示了现在如何在另一个项目中使用库,而不必指定依赖项目的版本:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.test</groupId>
  <artifactId>use</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>
 
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.test</groupId>
        <artifactId>bom</artifactId>
        <version>1.0.0</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.test</groupId>
      <artifactId>project1</artifactId>
    </dependency>
    <dependency>
      <groupId>com.test</groupId>
      <artifactId>project2</artifactId>
    </dependency>
  </dependencies>
</project>

 最后,在创建导入依赖项的项目时,请注意以下事项:

  • 请勿尝试导入在当前POM的子模块中定义的POM。尝试这样做将导致构建失败,因为它将无法定位POM。
  • 切勿将导入POM的POM声明为目标POM的父级(或祖级等)。无法解析循环,将引发异常。
  • 当引用其POM具有可传递依赖性的工件时,项目需要将这些工件的版本指定为托管依赖性。不这样做会导致生成失败,因为工件可能没有指定版本。(在任何情况下,这都应该被视为最佳实践,因为它防止工件的版本从一个构建更改到下一个构建)。

从Maven4.0开始,引入了一种新的特定BOM打包。它允许定义一个BOM表,该BOM表在利用较新的4.1.0模型的项目中不用作父级,同时仍然提供与Maven 3.X客户端和项目的完全兼容性。在安装/部署时,利用Maven 4的构建/消费者POM功能,将此BOM打包转换为更常见的POM打包。因此,这提供了与Maven 3.x的完全兼容性。

<project xmlns="http://maven.apache.org/POM/4.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.1.0 http://maven.apache.org/xsd/maven-4.1.0.xsd">
  <parent>
    <groupId>com.test</groupId>
    <version>1.0.0</version>
    <artifactId>parent</artifactId>
  </parent>
  <groupId>com.test</groupId>
  <artifactId>bom</artifactId>
  <version>1.0.0</version>
  <packaging>bom</packaging>
  <properties>
    <project1Version>1.0.0</project1Version>
    <project2Version>1.0.0</project2Version>
  </properties>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.test</groupId>
        <artifactId>project1</artifactId>
        <version>${project1Version}</version>
      </dependency>
      <dependency>
        <groupId>com.test</groupId>
        <artifactId>project2</artifactId>
        <version>${project2Version}</version>
      </dependency>
    </dependencies>
  </dependencyManagement>
 </project>
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值