Maven的坐标和依赖
1 Maven坐标
1.1 什么是Maven坐标
Maven坐标:世界上任何一组构件都可以使用Maven坐标来唯一标识,Maven坐标的元素包括groupId、artifactId、version、packaging、classifier。只要我们提供正确的坐标元素,Maven就能找到对应的构件。
那么Maven是从哪里下载构件的呢?
答案其实很简单,Maven内置了一个中央仓库的地址,该中央仓库包含了世界上大部分流行的开源项目构件,Maven会在需要的时候去那里下载。
在我们开发自己项目的时候,也需要为其定义适当的坐标,这是Maven强制要求的。在这个基础上,其他Maven项目才能引用该项目生成的构件。
1.2 坐标元素详解
- groupId:定义了当前Maven项目隶属于的公司组织和实际项目。groupId的表示方式与Java包名的表示方式类似,通常用域名反向一一对应,比如有一个Maven项目隶属于mycom公司的myapp项目,那么groupId就应该是com.mycom.myapp。
- artifactId:定义了实际项目中的一个Maven项目(模块),推荐的做法是使用实际项目名称作为artifactId的前缀,这样做的好处是便于寻找构件。因为默认情况下,Maven生成的构件,其文件名会以artifactId作为开头,比如myapp-core.1.0.0.jar,使用实际项目名称作为前缀后,就能方便的从一个lib文件夹中找到某个项目的一组构件。
- version:定义了Maven项目当前所处的版本。需要注意的是Maven定义了一整套版本规范,以及快照(SNAPSHOT)的概念,以后会详细介绍。
- packaging:定义了Maven项目的打包方式。默认是jar。
- classifier:定义了帮助定义构建输出的一些附属构件。
上述5个元素中,groupId、artifactId、version是必须定义的,packaging是可选的,而classifier是不能直接定义的。
2 Maven依赖
2.1 依赖的配置
一个依赖的声明可以包含如下的元素:
<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:依赖范围,见2.2。
- optional:标记依赖是否可选,见2.6。
- exclusions:用来排除传递性依赖,见2.。
大部分依赖声明只包含基本坐标,但是在某些特殊情况下,其他元素也是至关重要。
2.2 依赖范围
Maven因为执行一系列编译、测试和部署运行等操作,在不同的操作下使用的classpath不同,依赖范围就是用来控制依赖与三种 classpath(编译classpath、测试classpath、运行classpath)的关系,Maven有以下几种依赖范围:
- compile:编译依赖范围(默认依赖范围),使用此依赖范围对于编译、测试、运行三种 classpath 都有效,即在编译、测试和运行的时候都要使用该依赖jar包。
- test:测试依赖范围,从字面意思就可以知道此依赖范围只能用于测试classpath,而在编译和运行项目时无法使用此类依赖,典型的是JUnit,它只用于编译测试代码和运行测试代码的时候才需要。
- provided:已提供依赖范围,对于编译和测试classpath有效,而对运行时classpath无效,比如:servlet-api,因为servlet-api,tomcat等web服务器中已经存在,如果再打包进去,那么包之间就会冲突。
- runtime:运行时依赖范围,对于测试和运行classpath有效,但是在编译主代码时无效,典型的就是JDBC驱动实现。
- system:系统依赖范围,使用system范围的依赖时必须通过systemPath元素显示地指定依赖文件的路径,不依赖Maven仓库解析,并且往往与本机绑定,所以可能会造成建构的不可移植,谨慎使用。
依赖范围与classpath的关系:
2.3 传递性依赖
假设有如下依赖关系:
A->B(compile) 第一关系: a依赖b compile
B->C(compile) 第二关系: b依赖c compile
当在A中配置
<dependency> <groupId>com.B</groupId> <artifactId>B</artifactId> <version>1.0</version> </dependency>
则A中会自动导入C包。
有了传递性依赖机制,Maven会解析各个直接依赖的pom,将那些必要的间接依赖,以传递性依赖的形式引入到当前的项目中。
2.4 传递性依赖和依赖范围
假设A依赖B,B依赖C,那么A对于B是第一直接依赖,B对C是第二直接依赖,A对C是传递性依赖。
第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围,如下表:
2.5 依赖调解
Maven依赖调解的第一原则是:路径最近者优先。
Maven依赖调解的第二原则是:第一声明者优先。
例1:
A->B->C->X(1.0)
A->D->X(2.0)
此时Maven按照最短路径选择导入x(2.0)。
例2:
A->B->X(1.0)
A->D->X(2.0)
路径长度一致,则优先选择第一个声明的依赖,此时导入x(1.0)。
2.6 可选依赖
在声明依赖时,使用<optional>元素表示依赖为可选依赖。
<optional>true</optional>
可选依赖作用:
如果存在依赖A->B、B->X(可选)、B->Y(可选)。如果这三个依赖都是compile的,X、Y就是A的传递性依赖,但是当X、Y是可选依赖时,X、Y将不会成为A的依赖。
2.7 排除依赖
当A->B->C(1.0)时,此时在A项目中,如不想使用C(1.0),而使用C(2.0),则需要使用exclusion排除B对C(1.0)的依赖。并在A中引入C(2.0)。
<!--排除B对C的依赖--> <dependency> <groupId>B</groupId> <artifactId>B</artifactId> <version>0.1</version> <exclusions> <exclusion> <groupId>C</groupId> <artifactId>C</artifactId><!--无需指定要排除项目的版本号--> </exclusion> </exclusions> </dependency> <!---在A中引入C(2.0)--> <dependency> <groupId>C</groupId> <artifactId>C</artifactId> <version>2.0</version> </dependency>
2.8 归类依赖
<properties> <org.springframework-version>4.0.0.RELEASE</org.springframework-version> </properties> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${org.springframework-version}</version> </dependency>
2.9 依赖关系的查看
Maven会自动解析所有项目的直接依赖和传递性依赖,并且根据规则正确判断每个依赖的范围,对于一些依赖冲突,也能进行调节,以确保任何一个构件只有唯一的版本在依赖中存在,这些工作之后,最后得到那些依赖被称为已解析依赖。
cmd进入工程根目录,执行mvn命令:
- mvn dependency:list 查看当前项目的已解析依赖
- mvn dependency:tree 查看当前项目依赖树
- mvn dependency:analyze 分析依赖关系
参考:
《Maven实战》