《Maven官方文档》-Maven依赖机制简介
原文地址 译者:Tyrian
依赖机制是Maven最为用户熟知的特性之一,同时也是Maven所擅长的领域之一。单个项目的依赖管理并不难,
但是当你面对包含数百个模块的多模块项目和应用时,Maven能帮你保证项目的高度控制力和稳定性。
大纲:
- 排除、可选依赖
- 依赖范围
- 依赖管理
- 导入依赖
- 系统依赖
传递性依赖
传递性依赖是Maven2.0的新特性。假设你的项目依赖于一个库,而这个库又依赖于其他库。你不必自己去找出所有这些依赖,你只需要加上你直接依赖的库,Maven会隐式的把这些库间接依赖的库也加入到你的项目中。这个特性是靠解析从远程仓库中获取的依赖库的项目文件实现的。一般的,这些项目的所有依赖都会加入到项目中,或者从父项目继承,或者通过传递性依赖。
传递性依赖的嵌套深度没有任何限制,只是在出现循环依赖时会报错。
传递性依赖会导致包含库的依赖图增长的非常大。为了解决这个问题,Maven也提供了额外的机制,能让你指定哪些依赖会被包含:
- 依赖调解 – 当项目中出现多个版本构件依赖的情形,依赖调解决定最终应该使用哪个版本。目前,Maven 2.0只支持“短路径优先”原则,意思是项目会选择依赖关系树中路径最短的版本作为依赖。当然,你也可以在项目POM文件中显式指定使用哪个版本。值得注意的是,在Maven2.0.8及之前的版本中,当两个版本的依赖路径长度一致时,哪个依赖会被使用是不确定的。不过从Maven 2.0.9开始,POM中依赖声明的顺序决定了哪个版本会被使用,也叫作”第一声明原则”。
- “短路径优先”意味着项目依赖关系树中路径最短的版本会被使用。例如,假设A、B、C之间的依赖关系是A->B->C->D(2.0)和A->E->(D1.0),那么D(1.0)会被使用,因为A通过E到D的路径更短。但如果你想要强制使用D(2.0),那你也可以在A中显式声明对D(2.0)的依赖。
- 依赖管理 – 在出现传递性依赖或者没有指定版本时,项目作者可以通过依赖管理直接指定模块版本。之前的章节说过,由于传递性依赖,尽管某个依赖没有被A直接指定,但也会被引入。相反的,A也可以将D加入<dependencyManagement>元素中,并在D可能被引用时决定D的版本号。
- 依赖范围 – 你可以指定只在当前编译范围内包含合适的依赖。 下面会介绍更多相关的细节。
- 排除依赖 – 如果项目X依赖于项目Y,项目Y又依赖项目Z,项目X的所有者可以使用”exclusion”元素来显式排除项目Z。
- 可选依赖 – 如果项目Y依赖项目Z,项目Y的所有者可以使用”optional”元素来指定项目Z作为X的可选依赖。那么当项目X依赖项目Y时,X只依赖Y并不依赖Y的可选依赖Z。项目X的所有者也可以根据自己的意愿显式指定X对Z的依赖。(你可以把可选依赖理解为默认排除)。
依赖范围
依赖范围会影响传递性依赖,同时也会影响项目构建任务中使用的classpath。
Maven有以下6种依赖范围:
- compile
这是默认范围。如果没有指定,就会使用该依赖范围。编译依赖对项目所有的classpath都可用。此外,编译依赖会传递到依赖的项目。 - provided
和compile范围很类似,但provided范围表明你希望由JDK或者某个容器提供运行时依赖。例如,当使用Java EE构建一个web应用时,你会设置对Servlet API和相关的Java EE APIs的依赖范围为provided,因为web容器提供了运行时的依赖。provided依赖只对编译和测试classpath有效,并且不能传递。 - runtime
runtime范围表明编译时不需要依赖,而只在运行时依赖。此依赖范围对运行和测试classpath有效,对编译classpath无效。 - test
test范围表明使用此依赖范围的依赖,只在编译测试代码和运行测试的时候需要,应用的正常运行不需要此类依赖。 - system
系统范围与provided类似,不过你必须显式指定一个本地系统路径的JAR,此类依赖应该一直有效,Maven也不会去仓库中寻找它。 - import(Maven2.0.9及以上)
import范围只适用于pom文件中的<dependencyManagement>部分。表明指定的POM必须使用<dependencyManagement>部分的依赖。因为依赖已经被替换,所以使用import范围的依赖并不影响依赖传递。
每类依赖范围(除了import)通过不同方式影响传递性依赖,具体如下表所示。最左侧一列代表了直接依赖范围,最顶层一行代表了传递性依赖的范围,行与列的交叉单元格就表示最终的传递性依赖范围。表中的“-“表示该传递性依赖将会被忽略。
| compile | provided | runtime | test |
compile | compile(*) | – | runtime | – |
provided | provided | – | provided | – |
runtime | runtime | – | runtime | – |
test | test | – | test | – |
(*)注意:这里本来应该是compile范围,那样的话compile范围都必须显式指定-然而,有这样一种情况,你依赖的、继承自其它库中的类的库必须在编译时可用。考虑到这个原因,即使在依赖性传递情况下,编译时依赖仍然是compile范围。
依赖管理
Maven提供了一个机制来集中管理依赖信息,叫做依赖管理元素”<dependencyManagement>”。假设你有许多项目继承自同一个公有的父项目,那可以把所有依赖信息放在一个公共的POM文件,并且在子POM中简单第引用该构件即可。