Maven-Dependency Mechanism

依赖管理是maven的一个广为人知的特性, 这也是maven擅长的一个领域. 为单一的工程管理依赖不是很难, 但当你着手处理多模块工程和包含数十或数百个模块的应用时, maven可以帮助你很好地保持高度控制和稳定性.

Transitive Dependencies

依赖传递性是 maven 2.0中的一个新特性. 这不需要你去搜索和指定你自己的依赖需要的库, 而是自动地包含它们.

这个特性通过从指定的远程仓库读取你依赖的工程文件. 通常, 那些工程的所有依赖关系都会用于你的工程, 因为该项目从其父母或依赖关系中继承了这些. 

没有限制依赖的组织层次数目, 但如果发现一个循环依赖就会发生问题.

由于依赖传递, 包含库的关系图会迅速变得很大. 因此, 有一些额外的特性来限制哪些依赖被包含:

(1) Dependency mediation. 依赖调解. 这决定了, 当一个构件的多个版本相遇时, 使用哪个版本的依赖. 目前, maven 2.0 只支持使用最近的定义("nearest definition"), 就是说, 它将使用在依赖树中离你的项目最近的版本. 你可以显式地在项目的pom文件中声明, 保证总是使用某个特定的版本. 注意, 如果2个依赖版本在依赖树中的深度一样, 在maven 2.0.8之前, 它没有定义哪个版本会胜出, 但从maven 2.0.9, 由声明的顺序来决定, 第一声明者优先.

nearest definition, 是指在依赖树中离你的工程最近的那个版本. 比如, 如果A,B,C之间的依赖关系为: A->B->C->D 2.0, 以及 A->E->D 1.0, 这样, 当构建A时, 将会使用 D 1.0. 因为从A到D的路径, 后者更短.  你可以显式地在A中添加一个依赖 D 2.0, 强制使用D 2.0.

(2) Dependency management. 依赖管理. 当构件在依赖传递中 或没有指定版本的依赖中相遇时, 工程作者可以直接 指定使用构件的哪个版本. 在上一节的例子中, 一个依赖会被添加到A中, 即使它不是直接被A使用. 相反, A可以在它的依赖管理中包含D作为一个依赖, 直接控制D在被引用时使用哪个版本.

(3) Dependency scope. 依赖范围. 只包含适合当前构建阶段的依赖. 下面会详细描述这一点.

(4) Excluded dependencies. 排除依赖. 如果工程X依赖工程Y, 工程Y依赖工程Z, 工程X的所有者可以显式地排除工程Z作为一个依赖, 使用 exclusion 元素 .

(5) Optional dependencies. 可选依赖. 如果工程Y依赖工程Z, 工程Y的所有者可以标记工程Z作为一个可选依赖, 使用optional标签. 当工程X依赖工程Y时, X将只依赖Y, 而不依赖Z. X的所有者也可显式地添加Z作为一个依赖. 这有助于将可选依赖理解为默认的排除依赖.

Dependency Scope

依赖范围用于限制一个依赖的传递性, 也用于影响各种构建任务使用到的类路径classpath.

有6种可用范围:

(1) compile. 编译依赖范围. 这是默认的范围, 如果没有指定范围, 默认使用之. 在一个工程的所有类路径中, 编译依赖都有效. 包括:编译, 测试, 运行三种classpath.   此外, 这些依赖会扩散至依赖项目中.

典型例子: spring-core, 在编译测试运行时都需要这种依赖.

(2) provide. 已提供依赖范围. 与编译依赖范围很像, 但意味着你期望JDK或者一个容器来提供运行时依赖.  例如, 当构建一个JAVA企业级的WEB应用时, 你将设置基于servlet api和相关的java ee api的provide依赖, 但在运行项目时, 因为web容器提供这些类, 就不需要maven重复地引入了.. 这个范围只对编译和测试classpath有效, 且不会传递.

典型例子: servlet-api, 在运行项目时由容器提供.

(3) runtime. 运行时依赖范围. 这种范围在编译期不需要, 而运行时需要. 在运行和测试classpath中生效, 而编译classpath不生效.

典型例子: JDBC驱动的实现. 项目主代码的编译只需要JDK提供的JDBC接口, 在执行测试或运行时才需要具体实现.

(4) test. 测试依赖范围. 应用的正常使用时不需要, 只在测试时的编译和运行阶段生效.

典型例子: JUnit, 只有在编译测试代码和运行测试时才需要.

(5) system. 系统依赖范围. 类似于provided, 期望由你来显式提供JAR. 构件总是有效的, 而不需要去仓库中查找. 通过 systemPath元素来显式指定依赖文件的路径, 可以引用环境变量. 不需要maven仓库解析, 往往与本机系统绑定, 可能造成构建的不可移植, 谨慎使用.

(6) import. 导入依赖范围. (only available in Maven 2.0.9 or later) 只用于依赖是一个POM时.  指定的POM应该被当前工程的POM文件中的<dependencyManagement>节中定义的依赖关系取代. 因为他们被替代了, import依赖不会影响一个依赖的传递性. 也就是说, 对3种classpath没有实际影响.

每一种范围, 除了import, 以不同的方式影响着依赖传递性. 如下表所示.

image

Dependency Management

依赖管理部分是集中了依赖信息的机制. 当你有一组继承自同一父工程的子工程时, 可以把所有依赖信息都放到共同POM中, 而在子POM中简单地引用这些工程.

但存在一个问题: 依赖是会被继承的. 我们可以确定多个子模块中可能包含了若干依赖, 但无法确定某个子模块一定需要全部的依赖.

maven提供的 dependency management元素 既让子模块依赖父模块的配置, 又能保证子模块依赖使用的灵活性. 在dependencyManagement元素下的依赖声明不会引入实际的依赖, 不过它能约束 dependencies下的依赖使用.

(1) 将完整的依赖声明包含在父POM中, 子模块只需要配置简单的groupId和artifactId就能获得对应的依赖信息. 引入正确的依赖.

好处: 父POM中使用dependencyManagement声明依赖能够统一项目范围中依赖的版本, 当依赖版本在父POM声明之后, 子模块就无须声明 , 也不会发生多个子模块中版本不一致的情况. 有助于降低依赖冲突的几率.

注意, 如果子模块不声明依赖的使用, 即使该依赖声明在父POM的dependencyManagement中, 也不会产生任何实际效果. ---灵活性.

给出2个POM, 它们继承自同一父工程.

这两个示例POM共享了一个共同的依赖, group-a:artifact-b:1.0.

image

image

上述工程的依赖可以合并到父POM中, 然后这两个子POM就可以简化:

image

image

image

注意: 在这些依赖引用中, 要特别指定<type>元素. 因为, 从dependencyManagement部分匹配一个依赖引用 的最小信息集实际上是:{groupId, artifactId, type, classifier}. 在很多情况下,如果没有指定的话, 这些依赖将指向jar构件.

因为默认的type是jar, 而classifier是null, 所以我们可以使用 {groupId, artifactId} 作为标识的最小集合. 当type不是jar时, 需要特别指出.

(2) 在依赖传递中控制构件的版本.

image

image

上例中, project B继承自project A.

dependency标签会被自动继承,  而dependencyManagement标签不会.

在子项目中的dependency中引入a, c 时, 可以指定版本和范围, 由于依赖调解, 会覆盖父POM中的定义.

注意, b 在父项目中定义了, 如果它在a, c中的pom中被引用的话, 就会使用该定义.

而d 在子项目中的dependencyManagement中定义的, 如果d 在a或c中引用 , 那就会使用这个定义, 因为依赖管理优先于依赖调解, 也因为当前pom的声明优先于父pom的声明.

Importing Dependencies

The features defined in this section are only available in Maven 2.0.9 or later.

前面提到的import依赖范围只有在dependencyManagement元素下才有效果. 使用该范围的依赖通常指向一个POM, 作用是将目标POM中的dependencyManagement配置导入并合并到当前POM的dependencyManagement元素中.

image

上图中的project B与前一节中的示例的区别在于, 本例没有继承工程A, 而是通过import依赖范围, 在dependencyManagement部分导入工程A中的dependencyManagement内容, 除了d以外. 因为在本例的dependencyManagement中也有定义了d.

imageimage

image

上例中, Z导入了X,Y的依赖管理. 注意到X和Y都有依赖a, 且两者的版本不同. 这里将使用X中的版本, 因为X是先定义的, 并且Z中没有定义a.

import是非常有效的, 当用于定义一个多项目构建的相关构件库时. 一个工程使用一个或多个这些库里的构件是非常常见的. 然而,有时很难保持工程中使用的构件版本同步. 因为版本号分布在整个库中. 下面的模式阐述了 一个BOM如何创建并用于其他工程.

工程的根是BOM pom. 它定义了所有构件的版本号. 其他使用了该库的工程应该在它们自己的POM的dependencyManagement部分中导入.

 image

父子工程使用BOM pom作为它的父亲. 它是一个正常的复合pom.

image

实际的项目pom:

image

下面的工程演示了如何在其他项目中使用上述库, 而不需要指定依赖工程的版本号:

image

注意:

不要试图导入一个定义在当前POM中的子模块的POM. 这样将会导致构建失败, 因为它无法定位该POM.

不要将导入的POM声明为父或祖父POM. 无法处理循环, 将会抛出一个异常.

当引用的构件所属的POM存在依赖传递时, 该工程需要在依赖管理中指定版本号. 如果不这样做, 将导致 一个构建失败, 因为构件可能没有指定的版本号. 这应该视为一个最佳实践, 在任何情况下. 因为它保证了构件的版本变来变去的.

System Dependencies

范围为 system 的依赖, 总是有效的, 不需要到仓库中搜索. 它们通常用于告诉maven, 哪些依赖由jdk 或vm提供. 典型应用是JDBC标准扩展, 或JAAS.

Reference

http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Transitive_Dependencies

<Maven实战>

原文地址:https://www.cnblogs.com/lddbupt/p/5537137.html