maven的一个依赖声明可以包含以下一些基本元素
<project>
...
<dependencies>
<dependency>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<type>...</type>
<scope>...</scope>
<optional>...</optional>
<exclusions>
<exclusion>
...
</exclusion>
...
</exclusions>
</dependency>
...
</dependencies>
...
</project>
根元素project下的dependencies可以包含一个或者多个dependency元素,以声明一个或者多个项目依赖。每个依赖可以包含的元素有:
groupId、artifactId和version:依赖的基本坐标,对于任何一个依赖来说,基本坐标是最重要的,Maven根据坐标才能找到相应的依赖。
type:依赖的类型,对应于项目坐标定义的packaging。大部分情况下,该元素不必声明,其默认这为jar。
scope:依赖的范围
optional:标记依赖是否可选
假设有这样一个依赖关系,项目A依赖于项目B,项目B依赖于项目X和Y,B对于X和Y的依赖都是可选的。A -> B、B -> X(可选)、B -> Y(可选),根据传递性依赖的定义,如果所有这三个依赖的范围都是compile,那么X、Y就是A的compile范围传递性依赖。然而,由于X、Y都是可选依赖,依赖不会得以传递。换句话说,X、Y将不会对A有任何影响。
为毛要使用可选依赖这一特性呢,可能项目B实现了两个特性,其中特性一依赖于X,特性二依赖Y,而且这两个特性是互斥的,用户不可能同时使用这两个特性。比如B是一个持久层隔离工具包,它支持多种数据库,包括MYSQL,PostgreSQL等,在构建工具包的时候,需要这两种数据库的驱动程序,但在使用这个工具包的时候,只会依赖一种数据库。
上述XML代码片段中,使用<optional>元素表示mysql-connector-java和postgresql这两个依赖为可选依赖,它们只会对当前项目B有影响,当其他项目依赖于B的时候,这两个依赖不会被传递。因此,当项目A依赖于项目B的时候,如果其实际使用基于MYSQL数据库,那么项目A就需要显式的声明mysql-connector-java这一依赖。
exclusions:用来排除传递性依赖
传递性依赖会给项目隐式的引入很多的依赖,这极大的简化了项目依赖的管理,但是有些时候这种特性也会带来问题。例如,当前项目有一个第三方依赖,而这个第三方依赖由于某些原因依赖了另外一个类库的SNAPSHOT版本,那么这个SNAPSHOT就会成为当前项目的传递性依赖,而SNAPSHOT的不稳定性会直接影响到当前项目。这时就需要排除掉该SNAPSHOT,并且在当前项目中声明该类库的某个正式发布版本。还有一些情况,可能想要替换某个传递性依赖,比如Sun JTA API,Hibernate依赖于这个jar,但是由于版权的因素,该类库不在中央仓库中,而Apache Geronimo项目有一个对应的实现。这时就可以排除Sun JTA API,再声明Geronimo的JTA API实现。
上述配置中,项目A依赖于项目B,但是由于一些原因,不想引入传递性依赖C,而是自己显示的声明对项目C1.1.0版本的依赖。
需要注意的是,声明exclusion的时候,只需要groupId和artifactId,而不需要version,这是因为只需要groupId和artifactId就能唯一定位依赖图中的某个依赖。换句话说,Maven解析后的依赖中,不可能出现groupId和artifactId相同,但是version不同的两个依赖。
大部分依赖声明只包含基本坐标,然而在一些特殊的情况下,其他元素至关重要。
Maven有以下几种依赖范围:
compile:
编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的Maven依赖,对于编译、测试、运行三种classpath都有效。典型的例子是spring-core,在编译、测试和运行的时候都需要使用该依赖。
test:
测试依赖范围。使用此依赖范围的Maven依赖,只对测试classpath有效,在编译主代码或者运行项目的使用时将无法使用此类依赖。典型的例子是JUnit,它只有在编译测试代码及运行测试的时候才需要。
provided:
已提供依赖范围。使用此依赖范围的Maven依赖,对于编译和测试classpath有效,但在运行时无效。典型的例子是servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器已经提供,就不需要Maven重复的引入一遍。
runtime:
运行时依赖范围。使用此依赖范围的Maven依赖,对于测试和运行classpath有效,但在编译主代码时无效。典型的例子是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。
system:
系统依赖范围。该依赖与三种classpath(编译classpath、测试classpath、运行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/rt.jar</systemPath>
</dependency>
import:
导入依赖范围。该依赖范围不会对三种classpath产生实际的影响。
依赖范围与classpath的关系
依赖范围(Scope) | 对于编译classpath有效 | 对于测试classpath有效 | 对于运行时classpath有效 | 例子 |
compile | Y | Y | Y | spring-core |
test | - | Y | - | JUnit |
provided | Y | Y | - | servlet-api |
runtime | - | Y | Y | JDBC驱动实现 |
system | Y | Y | - | 本地的,Maven仓库之外的类库文件 |