Maven的依赖配置有基本的groupId,artifactId和version等元素组成,其实一个依赖声明可以包含如下的
一些元素
...
...
...
依赖元素介绍
根元素project下的dependencies可以包含一个或者多个dependency元素,以声明一个或多个
项目依赖。每个依赖可以包含的元素有:
groupId,artifactId和version:依赖的基本坐标,对于任何一个依赖来说,基本坐标是最重要
的,Maven根据坐标才能找到需要的依赖。
type:依赖的类型,对应于项目坐标定义的packaging.大部分情况下,该元素不必声明,其默认值
为jar
scope:依赖的范围,如test,compile,provided
optional:标记依赖是否可选
exclusions:用来排除传递性依赖
大部分依赖声明只包含基本坐标,然而在一些特殊情况下,其他元素至关重要。
依赖范围
Maven在编译项目主代码的时候需要使用一套classpath.
Maven在编译和执行测试的时候会使用另外一套classpath。
Maven实际运行项目的时候,又会使用一套classpath。
依赖范围就是用来控制依赖与这三种classpath(编译classpath,测试classpath,运行classpath)的关系,
Maven有以下几种依赖范围:
compile:编译依赖范围,如果没有指定,就会默认使用该依赖范围。使用此依赖范围的Maven依赖,对于编译,测试,运行三种
classpath都有效。典型的例子是spring-core
test:测试依赖范围。使用此依赖范围的Maven依赖,只对于测试classpath有效。典型的例子是JUnit
provided:已提供依赖范围。使用此依赖范围的Maven依赖,对于编译和测试classpath有效,但在运行时无效
典型的例子是:servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器已经提供,就不需要
Maven重复地引入一遍。
runtime:运行时依赖范围,使用此依赖范围的Maven依赖,对于测试和运行classpath有效,但在编译主代码时无效。
典型的例子是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现
上述接口的具体JDBC驱动。
systemm:系统依赖范围。该依赖范围与三种classpath的关系,和provided依赖范围完全一致。但是,使用system范围的依赖时必须通过
systemPath元素显式的指定依赖文件的路径。由于此类依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能造成
构建的不可移植,因为应该谨慎使用。systemPath元素可以引用环境变量,如
import:(Maven2.0.9及以上):导入依赖范围,该依赖范围不会对三种classpath产生实际的影响,此依赖范围只在dependencyManagement
元素下才有效,使用该范围的依赖通常指向一个pom,作用是将目标POM中的dependencyManagement配置导入并合并到当前POM的
dependencyManagement元素中。
import范围依赖由于其特殊性,一般都是指向打包类型的pom的模块,如果有多个项目,它们使用
的依赖版本都是一致的,则就可以定义一个使用dependencyManagement专门管理依赖的POM,然后
在各个项目中导入这些依赖管理配置
传递性依赖和依赖范围
依赖范围不仅可以控制依赖与三种classpath的关系,还对传递性依赖产生影响。
如account-email对于spring-core的依赖范围是compile,spring-core对于commons-logging的依赖范围是compile,那么account-email对于commons-logging
这一传递性依赖的范围也就是compile。
假设A依赖于B,B依赖于C,则A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖。第一直接依赖的范围和第二直接依赖的范围决定了传递性
依赖的范围
Maven会解析各个直接依赖的POM,将那些必要的间接依赖以传递性依赖的形式引入到当前的项目中。
规律如下:
当第二直接依赖的范围是compile的时候,传递性依赖的范围与第一直接依赖的范围一致;
当第二直接依赖的范围是test的时候,依赖不会得以传递;
当第二直接依赖的范围是provided的时候,只传递第一直接依赖的范围也为provided的依赖,且传递性依赖的范围同样为provided;
当第二直接依赖的范围是runtime的时候,传递性依赖的范围与第一直接依赖的范围的范围一致,但compile例外,此时传递性依赖的范围为runtime;
依赖调解
Maven引入的传递性依赖机制,一方面大大简化和方便了依赖声明,另一方面,大部分情况下我们只需要关心项目的直接依赖是什么,而不用考虑这些直接依赖会引入
什么传递性依赖。但有时候,当传递性依赖造成问题的时候,就需要清楚地知道该传递性依赖是从哪条依赖路径引入的
依赖调解(Dependency Mediation)的原则有两个,如下:(为了避免针对同个构件的重复引入)
第一原则:路径最近者优先
第二原则:第一声明优先。 即在依赖路径长度相等的前提下,在POM中依赖声明的顺序决定了谁会被解析使用,顺序最靠前的那个依赖优先。
可选依赖
假设有这样一个依赖关系,项目A依赖于项目B,项目B依赖于项目X和项目Y,B对于项目X和Y的依赖都是可选依赖。根据传递性依赖的定义,如果所有这三个依赖的范围都是
compile,那么X\Y就是A的compile范围传递性依赖,然而,由于这里X\Y是可选依赖,依赖将不会得以传递,换句话说,X\Y将不会对A有任何影响。
在理想的情况下,是不应该使用可选依赖的。
使用可选依赖的原因是某一个项目实现了多个特性,如项目是一个持久层隔离工具包,支持多种数据如MYSQL,SYBASE,在构建这个工具包的时候,需要这两种数据库的驱动
程序,但在使用这个工具包的时候,只会依赖一种数据库。
排除依赖
传递性依赖会给项目隐式地引入很多依赖,这极大的简化了项目依赖的管理,但有时这种特性也会带来问题。
声明exclusion的时候只需要groupId和artifactId,而不需要version元素,这是因为只需要groupId和artifactId就能唯一确定依赖图中的某个依赖。
排除依赖:
排除不稳定的传递性依赖构件,另外再显式在当前项目中声明该类库的某个正式发布的版本。
想替换某个传递性依赖,比如Sun JTP API,Hibernate依赖于这个JAR,但是由于版本的因素,该类库不在中央仓库中,而Apache Geronimo项目有一个对应的实现
这时就可以排除Sun JAT API,再声明Geronimo的JTA API的实现
归类依赖
如果一个项目依赖同一项目的多个不同模块,如一个项目依赖springframework的多个模块,因为这些依赖的版本都是相同的,且可以预见,如果将来需要升级,则这些
依赖的版本会一直升级。
这样可以定义maven的属性,然后通过${}的方式引用
优化依赖:
Maven会自动解析所有项目的直接依赖和传递性依赖,并且根据规则正确判断每个依赖的范围,对于一些依赖,
也能进行调节,以确保任何一个依赖只有唯一的版本在依赖中存在,在这些工作之后,最后得到的那些依赖被称为
已解析依赖(Resolved Dependency). 可以运行如下命令查看当前项目的已解析依赖
mvn dependency:list
mvn dependency:tree
mvn dependency:analyze
坐标和依赖是任何一个构件在Maven世界中的逻辑表达方式;而构件的物理表示方式是文件,Maven通过仓库来统一管理这些文件