1.1何为maven坐标
Mavne的一大功能是管理项目依赖,为了能自动化的解析任何一个Java构件,maven就必须将它们唯一标识,这就依赖管理的底层基础—-坐标。
Maven的世界中拥有数量非常巨大的构件,也就是平时用的一些jar、war等文件,在Maven为这些构件引入坐标概念之前,我们无法使用任何一种方式来唯一标识所有这些构件。因此maven定义了这样组规则:世界上任何一个构件都可以使用Maven坐标唯一标识,maven坐标元素包括groupId、artifactId、version、packaging、classifier。现在,只要我们提供正确的坐标元素,maven就能找到对应的组件。比如说,当需要使用Java5平台上TestNG的5.8版本时,就告诉Maven:“groupId=org.testng;artifactId=testng;version=5.9;classifier=jdk15
”,maven就会从仓库中寻找相应的构件供我们使用。Maven是从哪里下载构件的呢?maven内置了一个中央仓库的地址(http://repol.maven.org/maven2),该中央仓库包含了世界上大部分流行的开源项目组件,maven会在需要的时候去那里下载。
1.2坐标详解
先看一组坐标定义,如下:
<groupId>org.sonatype.nexus</groupId>
<artifactId>nexus-indexer</artifactId>
<version>2.0.0</version>
<packaging>jar</packaging>
这是nexus-indexer的坐标定义,nexus-indexer是一个对maven仓库编纂索引并提供搜索功能的类库,它是Nexus项目的一个子模块。下面解释一下各个坐标元素:
- groupId:定义当前maven项目隶属的实际目录。首先,maven项目和实际项目不一定是一对一的关系。其次,groupId不应该对应项目隶属的组织或公司。最后,groupId的表示方式与Java包名的表示方式类似,通常与域名反向一一对应。
- artifactId:该元素定义实际项目中的一个Maven项目(模块),推荐的做法是使用实际项目名称作为前缀。
- version:该元素定义Maven项目当前所处版本。
- packaging:该元素定义Maven项目的打包方式。首先。打包方式通常与所生成构件的文件扩展名对应。其次,打包方式会影响到构件的生命周期,比如jar打包和war打包会使用不同的命令。最后,当不定义packaging的时候,Maven会使用默认值jar。
- classifier:该元素用来帮助定义构建输出的一些附属构件。附属构件与主构件对应,如上例中的主构件是nexus-indexer-2.0.0.jar,该项目可能会通过使用一些插件生成如nexus-indexer-2.0.0-javadoc.jar、nexus-indexer-2.0.0-sources.jar这样一些附属构件。
上述五个元素中,groupId、artifactId、version是必须定义的,packaging是可选的(默认为jar),而classifier是不能直接定义的。
理解清楚Maven坐标之后,我们就能开始讨论Maven的依赖管理了。
1.3依赖的配置
一个依赖声明可以包含如下的一些元素:
<project>
…
<dependencies>
<dependency>
<groupId>…</groupId>
<artifactId>…</artifactId>
<version>…</version>
<type>…</type>
<scope>…</scope>
<optional>…</optional>
<exclusions>
<exclusion>
…
</exclusion>
…
</exclusions>
</dependency>
…
</dependencies>
…
</project>
Dependencies可以包含一个或者多个dependency元素,以声明一个或者多个项目依赖。每个依赖可以包含的元素有:
- groupId、artifactId和version:依赖的基本坐标,对于任何一个依赖来说,基本坐标是最重要的,Maven根据坐标才能找到需要的依赖。
- type:依赖的类型,对应于项目坐标定义的packaging。大部分情况下,该元素不必声明,其默认值为jar。
- scope:依赖的范围。
- optional:标记依赖是否可选。
- exclusions:用来排除传递性依赖。
1.4依赖范围
首先需要知道,Maven在编译项目主代码的时候需要使用一套classpath。其次,Maven在编译和执行测试的时候会使用另外一套classpath。最后,实际运行Maven项目的时候,又会使用一套classpath。
依赖范围就是用来控制依赖与这三种classpath(编译classpaht、测试classpath、运行classpath)的关系,Maven有以下几种依赖范围:
- compile:编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的Maven依赖,对于编译、测试、运行三种classpath都有效。
- test:测试依赖范围。使用此范围依赖的Maven依赖,只对测试classpath有效,在编译主代码或者运行项目的时候将无法使用此类依赖
- provided:已提供依赖范围。使用此依赖范围的Maven依赖,对于编译和测试classpath都有效,但是在运行时无效
- runtime:运行时依赖范围。使用此依赖范围的Maven依赖,对于测试和运行classpath有效,但在编译主代码时无效。
- system:系统依赖范围。该依赖与三种classpath的关系,和provided依赖范围完全一致。但是,使用system范围的依赖时必须通过systemPath元素显式地指定依赖文件路径。由于此类依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能造成构件的不可移植,因此应该谨慎使用。SystemPath元素可以引用环境变量,如:
<dependency>
<groupId>javax.sql</groupId>
<artifactId>jdbc-stdext</artifactId>
<version>2.0</version>
<scope>system</scope>
<systemPath>${java.home}/lib/tr.jar</systemPath>
</dependency>
- import:导入依赖范围。该依赖范围不会对三种classpath产生实际的影响。
1.5传递性依赖
何为传递性依赖?现在举一个例子:项目中有一个compile范围的spring-core依赖,spring-core有一个compile范围的commons-logging依赖,那么commons-logging就会成为该项目的compile范围依赖,commons-logging是该项目的一个传递依赖。
Maven会解析各个直接依赖的POM,将那些必要的间接依赖以传递性依赖的形式引入到当前项目中。
依赖范围不仅可以控制依赖与三种classpath的关系,还对传递性依赖产生影响。假设A依赖于B,B依赖于C,我们说A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖。第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围,如表,最左边一列表示第一直接依赖范围,最上面一行表示第二直接依赖范围,中间的交叉单元格则表示传递性依赖范围
|
compile |
test |
provided |
runtime |
compile |
compile |
—— |
—— |
runtime |
test |
test |
—— |
—— |
test |
provided |
provided |
—— |
provided |
provided |
runtime |
runtime |
—— |
—— |
runtime |
仔细观察一下表可以发现这样的规律:当第二直接依赖的范围是compile的时候,传递性依赖的范围与第一直接依赖的范围一致;当第二直接依赖的范围是test的时候,依赖不会得以传递;当第二直接依赖的范围为provided的时候,只传递第一直接依赖的范围为provided的依赖,且传递性依赖的范围同样为provided;当第二直接依赖的范围是runtime的时候,传递性依赖的范围与第一直接依赖的范围一致,但compile除外,此时传递性依赖的范围为runtime。
1.6依赖调解
Maven引入的传递性依赖机制,一方面大大简化和方便了依赖声明,另一方面,大部分情况下我们只需要关心项目的直接依赖是什么,而不用考虑这些直接依赖会引入什么传递依赖。但是有时候,当传递性依赖造成问题的时候,我们就需要清楚的知道该传递性依赖是从哪条依赖路径引入的。
例如A->B->C->X(1.0)、A->D->X(2.0),X是A的传递性依赖,但是两条依赖路径上有两个版本的X,那个X会被Maven解析使用呢?Maven依赖调解的第一原则是:路径最近者优先。依赖调解第一原则不能解决所有问题,比如:A->B->Y(1.0)、A->C->Y(2.0),Y(1.0)和Y(2.0)的依赖路径长度是一样的,到底谁会解析?在Maven2.0.9开始,Maven定义了依赖调解的第二原则:第一声明者优先。
posted on 2012-03-06 09:24
邦 阅读(1313)
评论(0) 编辑 收藏 所属分类:
Maven