本节来学习Maven地依赖机制。
依赖关系管理是Maven的一个核心特性。管理单个项目的依赖关系很容易。管理由数百个模块组成的项目的依赖关系是可能的。Maven在定义、创建和维护具有良好定义地类路径和库版本的可重复构建方面帮助很大。
Maven通过自动包含传递依赖项,来避免查找和指定自己的依赖项所需要的库。
通过从指定的远程存储库中读取依赖项的项目文件,可以简化该特性。通常,这些项目的所有依赖项都在项目中使用,项目从其父项目继承的依赖项或从其依赖项继承的依赖项也是如此。g
可以从依赖项收集的几部数量没有限制,只有在发现循环依赖关系时才会出现问题。
通过传递依赖关系,所包含库地图可以快速增长到相当大。由于这个原因,还有一些附加特性限制了所包含的依赖关系:
虽然可传递依赖项可以帮助我们包含隐含的所需依赖项,但是最好是明确指定自己的源代码中所使用的依赖项目。当依赖的项目改变了它本身的依赖关系时,这个最佳实践证明了其价值。
比如项目A依赖了项目B,并且项目B依赖了项目C。如果直接使用项目C中的组件,而不是在项目A中指定项目C,则当项目B突然更新/删除项目C的依赖时,它可能会导致生成失败。
直接指定依赖关系的另一个原因是,它为我们的项目提供了更好的文档:可以通过阅读项目中的POM文件了解更多信息。
Maven还提供了 dependency:analyze
插件来分析这些依赖关系:它有助于使这种最佳实践更容易实现。
依赖范围用于限制依赖的传递性,并且还用于影响用于各种构建任务的类路径。
有6种scope可用:
如果没有指定的话,这是默认作用域。编译依赖项再项目的所有类路径中都可用。此外,这些依赖关系被传播到依赖的项目。
这非常类似于编译,但是表示希望JDK或容器再运行时提供依赖项。例如,在为Java企业应用构建Web应用程序时,您需要将Servlet API和相关Java EE API的依赖关系设置为provided范围,因为web容器提供了这些类。此作用域仅在编译和测试类路径中可用,并且是不可传递的。
此范围指示编译时不需要依赖项,而执行时需要。它位于运行时和测试类路径中,而不是编译路径中。
此范围表明该依赖关系不是应用程序正常使用所必需的,并且仅在测试编译和执行阶段可用。这个分为时不可传递的。
这个总用玉与provided作用域类似,指示必须提供显式包含它的JAR。项目重视i可用的,不会在存储库中查找。
这个作用域只支持打包类型为pom的 <dependencyManagement>
标签部分。它指示将依赖项替换为 <dependencyManagement>
标签中的有效依赖项列表。由于它们被替换,具有导入范围的依赖项实际上不参与限制依赖项地传递。
每个作用域(import除外)以不同的方式影响传递依赖关系,如下表所示。如果将依赖项设置为左列中的范围,则该依赖项与顶行作用域之间的可传递依赖项将导致主项目中的依赖项与交叉点处列出的作用域之间存在依赖关系。如果没有列出范围,则表示将忽略依赖项。
compile | provided | runtime | test | |
---|---|---|---|---|
compile | compile(*) | - | runtime | - |
provided | provided | - | provided | - |
runtime | runtime | - | runtime | - |
test | test | - | test | - |
(*)注意:这应该是运行时作用域,因此必须显式列出所有编译依赖项。但是,在这种情况下,所依赖的库从另一个库扩展了一个类,迫使我们在编译时具有可用性。因此,及时编译时依赖项是可传递的,他们仍然是编译范围。
依赖关系管理部分是一种集中依赖关系信息的机制。当我们有一组继承公共父类的项目时,可以将有依赖关系的所有信息按在公共POM中,使得子项目可以更简单地引用。通过例子可以很好地说明这种机制。考虑到两个pom扩展了相同的父pom:
Project 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>
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。多数情况下,这些依赖关系将引用没有classifier的jar项目,因此type字段默认为jar,classifier默认为null。
依赖关系管理部分的第二个非常重要的用途是控制可传递依赖关系中使用的版本,以这些项目为例:
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>
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版本1.0上运行时,无论其pom中指定的版本如何,都将使用a,b,c和d:
上面的示例描述了如何通过继承来指定托管依赖项。但是,在较大的项目中,可能无法实现此目标,因为项目只能继承单个父项目。为了适应这种情况,项目可以从其它项目导入托管依赖项。这是通过将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,最终结果将是相同的。除了d之外,所有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。在这里,将使用版本1.1,因为X首先被申明,而且在Z的dependencyManagement中没有申明。
这个过程是递归的。例如,如果X导入了另一个pom,Q。当处理Z时,将显式所有Q的依赖管理项都在X中定义。
通常当用于定义多个项目构建的一部分的相关工件的“库”时,导入最有效。一个项目使用这些库中的一个或多个工件是相当常见的。但是,有时很难使用与库中分发的版本同步的工件来保留项目中的版本。下面的模式说明了如何创建“物料清单”(BOM)以供其他项目使用。
项目的根是BOM pom。它定义了将在库中创建的所有工件的版本。希望使用该库的其他项目应将此pom导入其pom的dependencyManagement部分。
<projectxmlns="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>${project1Version}</version> </dependency> </dependencies> </dependencyManagement> <modules> <module>parent</module> </modules> </project>
parent子项目将BOM pom作为其父项目。这是一个正常的多项目pom。
<projectxmlns="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
<projectxmlns="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> <projectxmlns="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>
下面的项目展示了如何在另一个项目中使用这个库,而不必指定依赖项目的版本。
<projectxmlns="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>
最后,在创建导入依赖项的项目时,请注意以下事项:
重要提示:此标记已弃用。
范围系统的依赖关系始终可用,并且不会在存储库中查找。它们通常用于告诉Maven有关JDK或VM提供的依赖关系。因此,系统依赖性对于解决现在由JDK提供的工件的依赖性特别有用,但是可以在之前单独下载。典型示例是JDBC标准扩展或Java身份验证和授权服务(JAAS)。
一个简单的例子:
<project> ... <dependencies> <dependency> <groupId>javax.sql</groupId> <artifactId>jdbc-stdext</artifactId> <version>2.0</version> <scope>system</scope> <systemPath>${java.home}/lib/rt.jar</systemPath> </dependency> </dependencies> ... </project>
如果我们的工件由JDK的tools.jar提供,则系统路径将定义如下:
<project> ... <dependencies> <dependency> <groupId>sun.jdk</groupId> <artifactId>tools</artifactId> <version>1.5.0</version> <scope>system</scope> <systemPath>${java.home}/../lib/tools.jar</systemPath> </dependency> </dependencies> ... </project>