依赖机制是Maven用户最为熟悉的特性之一,也是Maven擅长的领域之一。 单个项目的依赖管理并不难。
但是,当快的汽车面临包含数百个模块的多模块项目和APP应用时,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-) D(1.0 ),则使用d ) 1.0 ),因为a从e到d的路径更短。 但是,如果想强制使用d(2.0 ),也可以在a中明确声明对d(2.0 )的依赖。
依赖关系管理-如果存在依赖关系,或者未指定版本,项目创建者可以通过依赖关系管理直接指定模块版本。 如的章所述,由于传递性依存,即使某依存没有被直接指定为a,也会被引入。 或者,a可以将d添加到元素中,并在可能引用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有六个依赖范围:
康普尔
这是默认范围。 如果未指定,则使用其从属范围。 编译依赖关系可用于项目的所有classpath。 它还将编译依赖传递给依赖的项目。
预配
与compile作用域类似,但预提供的作用域表示您希望从JDK或容器中提供运行时依赖关系。 例如,如果使用Java EE生成web APP应用程序,web容器将为servlet API和关联的Java EE APIs设置依赖关系,以提供运行时依赖关系。 provided依赖项只对classpath的编译和测试有效,不能传递。
运行时间
runtime范围表示它不需要在编译时依赖,而是只在运行时依赖。 此依赖关系对运行和测试classpath有效,而对编译classpath无效。
测试
test范围表示使用此依赖范围的依赖关系,仅在编译测试代码并运行测试时才需要该范围,而在正常执行APP应用程序时不需要该范围。
系统
系统范围类似于providence,但必须明确指定本地系统路径的JAR。 这种依赖关系必须始终有效,Maven不会在仓库里找它。
import(maven2.0.9或更高版本) )。
导入范围仅适用于pom文件的一部分。 指示指定的POM必须使用部分相关性。 使用import范围的从属关系不会影响从属关系,因为从属关系已经被替换。
除import外,每个类的依赖范围都以不同的方式影响传递相关性,如下表所示。 最左列表示直接依赖范围,最上面一行表示传递性依赖范围,行和列的交点xhdwdm表示最终的传递性依赖范围。 表中的“-”表示该传递性依赖关系将被忽略。
康普尔
预配
运行时间
测试
康普尔
公司(*
_
运行时间
_
预配
预配
-
provided
-
runtime
runtime
-
runtime
-
test
test
-
test
-
(*)注意:这里本来应该是compile范围,那样的话compile范围都必须显式指定-然而,有这样一种情况,你依赖的、继承自其它库中的类的库必须在编译时可用。考虑到这个原因,即使在依赖性传递情况下,编译时依赖仍然是compile范围。
依赖管理
Maven提供了一个机制来集中管理依赖信息,叫做依赖管理元素””。假设你有许多项目继承自同一个公有的父项目,那可以把所有依赖信息放在一个公共的POM文件,并且在子POM中简单第引用该构件即可。通过一些例子可以更好的解释这个机制。下面是两个继承自同一个父项目的POM:
项目A
…
group-a
artifact-a
1.0
group-c
excluded-artifact
group-a
artifact-b
1.0
bar
runtime
项目B
…
group-c
artifact-b
1.0
war
runtime
group-a
artifact-b
1.0
bar
runtime
这两个POM都依赖于同一个模块,同时每个POM又各自依赖于一个无关的模块。父项目的POM详细信息如下所示:
…
group-a
artifact-a
1.0
group-c
excluded-artifact
group-c
artifact-b
1.0
war
runtime
group-a
artifact-b
1.0
bar
runtime
这样两个子项目的POM文件就简单多了。
…
group-a
artifact-a
group-a
artifact-b
bar
…
group-c
artifact-b
war
group-a
artifact-b
bar
注意:在这两个POM文件的依赖中,我们必须指定元素。因为与依赖管理元素匹配的依赖引用最小信息集是{groupId, artifactId, type, classfier}。许多情况下,依赖指向的jar不需要指定classfier。因为默认type是jar,默认classfiler为空,所以我们可以把信息集设置为{groupId, artifactId}。
依赖管理元素第二个非常有用的功能是控制传递性依赖中构件的版本。例子如下
项目A:
4.0.0
maven
A
pom
A
1.0
test
a
1.2
test
b
1.0
compile
test
c
1.0
compile
test
d
1.2
项目B:
A
maven
1.0
4.0.0
maven
B
pom
B
1.0
test
d
1.0
test
a
1.0
runtime
test
c
runtime
当在maven中有项目依赖B时,不管它们的pom文件中指定的版本是什么,构件a,b,c和d的版本都是1.0。
a和c都被声明为这个项目的依赖,根据依赖调解,a和c的版本都是1.0.同时a和c的依赖范围都被显式指定为runtime。
b定义在B的父项目的元素中,因为在依赖性传递中优先于依赖调解,所以b的版本是1.0,b是编译依赖范围。
最后,d是定义在B的元素中。
依赖管理的标签详细描述信息可以从这里获取项目描述符引用
导入依赖
这个章节描述的特性只在Maven2.0.9及之后的版本才有。这意味着更早版本的Maven不会解析包含import元素的pom文件。因此在使用该特性前,你必须慎重考虑。如果你打算使用这个特性,我们建议你使用enforcer插件来强制使用Maven2.0.9及以上版本。
前面的例子描述了怎么通过继承来指定管理的依赖。然而,这对于更大的项目通常会更复杂,因为一个项目只能继承自一个父项目。为了解决这个问题,项目可以导入其他项目的管理依赖,这可以通过声明依赖一个包含值为”import”的元素的构件来实现。
项目B:
4.0.0
maven
B
pom
B
1.0
maven
A
1.0
pom
import
test
d
1.0
test
a
1.0
runtime
test
c
runtime
假设A就是上一个例子中定义的POM,那么最终的结果也是一致的。除了在B中定义的d模块,所有A的管理依赖都会导入到B中。
项目X:
4.0.0
maven
X
pom
X
1.0
test
a
1.1
test
b
1.0
compile
项目Y:
4.0.0
maven
Y
pom
Y
1.0
test
a
1.2
test
c
1.0
compile
项目Z:
4.0.0
maven
Z
pom
Z
1.0
maven
X
1.0
pom
import
maven
Y
1.0
pom
import
在上面的例子中,Z导入了X和Y的管理依赖。不过有个问题,X和Y都包含了依赖a。在这里,会使用1.1版本的a,因为X先被声明,并且a没有在Z的依赖管理中声明。
这个过程是递归进行的。假如X导入了另外的POM,Q,那么当解析Z的时候,所有Q的管理依赖看上去就都像在X中定义的一样。
当定义一个用于构建多项目的包含一些相关构件的依赖“库”时,导入依赖就十分有效。从“库”中引用一个或多个构件到项目中,是一种很常见的做法。然而,
保持项目中使用的依赖版本与库中发布的版本一致会有点麻烦。下面的模式描述了怎么生成一个供其它项目使用的“物料清单”(BOM)。
项目的根元素是BOM pom文件。它定义了库中创建的所有构件版本。其它要使用该库的项目必须将该pom导入到其pom文件中的元素中。
4.0.0
com.test
bom
1.0.0
pom
1.0.0
1.0.0
com.test
project1
${project1Version}
com.test
project2
${project1Version}
parent
parent子项目将BOM pom作为它的父项目。这是一个简单的多项目pom。
4.0.0
com.test
bom
1.0.0
pom
1.0.0
1.0.0
com.test
project1
${project1Version}
com.test
project2
${project1Version}
parent
接下来是真正的pom文件。
4.0.0
com.test
1.0.0
parent
com.test
project1
${project1Version}
jar
log4j
log4j
4.0.0
com.test
1.0.0
parent
com.test
project2
${project2Version}
jar
commons-logging
commons-logging
下面的例子说明了怎么在项目中使用“库”,而不必指定依赖模块的版本。
4.0.0
com.test
use
1.0.0
jar
com.test
bom
1.0.0
pom
import
com.test
project1
com.test
project2
最后,当创建引入依赖的项目时,需要注意以下几点:
不要尝试引入在当前pom中定义的子模块pom。那会导致不能定位pom和编译失败。
绝不要声明导入其他pom作为目标pom的父项目(或者冷艳的泥猴桃项目等)的pom文件。这会导致循环解析,并触发异常。
当引用有传递性依赖的模块时,需要指定依赖模块的版本。不这样做,这些模块可能没有确定的版本,从而导致编译失败。(这在任何情况下都应该是一个最佳实践,因为它保证了模块版本的不变性)
系统依赖
系统范围的依赖应该是一直可用,并且Maven不会去仓库中查找。系统范围依赖通常是指JDK或者VM提供的依赖。所以,系统依赖适用于这种情况:以前可以单独获取,但现在是由JDK提供的依赖。典型的例子就是JDBC标准扩展或者Java认证和授权服务(JAAS)。一个简单的例子如下:
…
javax.sql
jdbc-stdext
2.0
system
${java.home}/lib/rt.jar
…
如果你的构件依赖于JDK的tools.jar,那么系统路径的值如下所示:
…
sun.jdk
tools
1.5.0
system
${java.home}/../lib/tools.jar
…