依赖关系管理是Maven的核心功能。管理单个项目的依赖关系很容易。管理由数百个模块组成的多模块项目和应用程序的依赖关系是可能的。Maven在定义、创建和维护具有良好定义的类路径和库版本的可复制构建方面有很大帮助。
Maven通过自动包含可传递的依赖关系,避免了发现和指定您自己的依赖关系所需的库的需要。
通过从指定的远程存储库中读取依赖项的项目文件,可以实现此功能。通常,这些项目的所有依赖项都会在项目中使用,项目从其父项或依赖项继承的任何依赖项也是如此,依此类推。
可以从中收集依赖关系的级别数量没有限制。只有当发现循环依赖关系时,才会出现问题。
有了可传递的依赖关系,包含库的图可以很快变大。因此,还有一些附加功能限制了所包含的依赖项:
- 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指定了对另一个项目B的依赖项,而项目B指定了对项目C的依赖项。如果您直接在项目C中使用组件,而您没有在项目A中指定项目C,则当项目B突然更新/删除其对项目C的依赖时,可能会导致生成失败。
直接指定依赖项的另一个原因是它为您的项目提供了更好的文档:只需读取项目中的POM文件,或者执行mvn-dependency:tree,就可以了解更多信息。
Maven还提供了dependency:analyze插件分析依赖关系的目标:这有助于使这种最佳实践更容易实现。
依赖关系范围用于限制依赖关系的传递性,并确定依赖关系何时包含在类路径中。
共有6个作用域:
这是默认范围,如果未指定,则使用。编译依赖项在项目的所有类路径中都可用。此外,这些依赖项将传播到依赖项目。
这很像编译,但表示您希望JDK或容器在运行时提供依赖关系。例如,当为Java Enterprise Edition构建web应用程序时,您需要将对Servlet API和相关Java EE API的依赖设置为所提供的范围,因为web容器提供了这些类。具有此作用域的依赖项被添加到用于编译和测试的类路径中,但不添加到运行时类路径中。它不可传递。
此范围表示编译不需要依赖项,而是执行依赖项。Maven在运行时和测试类路径中包含一个具有此作用域的依赖项,但不包括编译类路径。
此范围表示应用程序的正常使用不需要依赖项,仅适用于测试编译和执行阶段。此范围不可传递。通常,这个范围用于JUnit和Mockito等测试库。它也用于非测试库,如Apache Commons IO,如果这些库用于单元测试(src/test/java),但不用于模型代码(src/main/java)。
这个作用域与提供的作用域类似,只是您必须提供显式包含它的JAR。工件总是可用的,并且不会在存储库中查找。
只有
每个作用域(导入除外)都以不同的方式影响可传递的依赖项,如下表所示。如果将依赖项设置为左列中的作用域,则该依赖项与顶行中作用域的可传递依赖项将导致主项目中的依赖项与交叉点处列出的作用域。如果没有列出作用域,则表示省略了依赖项。
| 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>
注意:在其中两个依赖引用中,我们必须指定
依赖关系管理部分的第二个也是非常重要的用途是控制可传递依赖关系中使用的工件的版本。以这些项目为例:
项目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版本。
有关依赖项管理标记的引用信息可从项目描述符引用中获得。
上一节中的示例描述了如何通过继承指定托管依赖项。然而,在较大的项目中,这可能是不可能实现的,因为一个项目只能从单亲继承。为了适应这种情况,项目可以从其他项目导入托管依赖项。这是通过将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表”(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>
最后,在创建导入依赖项的项目时,请注意以下事项:
从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>