Maven坐标
Maven坐标为各种构件引入了秩序,任何一个构件都必须明确自己的坐标,一组坐标是通过一些元素定义的。一组坐标定义如下:
<groupId>com.redsoft.spirit</groupId>
<artifactId>spirit-jpa</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
元素解释:
- groupId:定义当前Maven项目隶属的实际项目。
- artifactId:定义实际项目中的一个Maven项目(模块)
- version:定义Maven项目当前所处的版本
- packaging:定义Maven项目的打包方式。打包方式通常与所生成构件的文件扩展名对应。其次,打包方式会影响到构建的生命周期(jar包和war包使用不同的命令)。不定义packaging的时候,默认值==jar==。
- classifier:用来帮助定义构建输出的一些附属构件。附属构件与主构件对应。不能直接定义。
项目构件的文件名是与坐标相对应的。一般规则为:artifactId-version[-classifier].packaging。packaging并非一定与构件扩展名对应,比如packaging为maven-plugin的构件扩展名为jar。
依赖
一个依赖的声明可以包含如下的一些元素:
<project>
...
<dependencies>
<dependency>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<packaging>...</packaging>
<type>...</type>
<scope>...</scope>
<optional>...</optional>
<exclusions>
<exclusion>
...
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
基本元素讲解(基本坐标讲过的不在赘述)
type 依赖的类型
-对应于项目坐标定义的packaging,大部分情况下,该元素不必声明,默认jar
scope 依赖的范围
- compile:编译依赖范围。如果没有指定,默认使用该依赖范围。对于编译、测试、运行三种classpath都有效。
- test:测试范围依赖。只对测试classpath有效,在编译主代码或者运行项目的使用时将无法使用此类依赖。
- provided:已提供依赖范围。对于编译和测试classpath都有效,但在运行时无效。
- runtime:运行时依赖范围。对于测试和运行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/rt.jar</systemPath>
</dependency>
- import(2.0.9及以上):导入依赖范围。不会对三种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仓库之外的类库文件 |
传递性依赖和依赖范围
依赖范围不仅可以控制依赖与三种classpath的关系,还对传递性依赖产生影响。
假设A依赖于B,B依赖于C,我们说A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖。第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围。
compile | test | provided | runtime |
---|---|---|---|
compile | compile | – | – |
test | test | – | – |
provided | provided | – | provided |
runtime | runtime | – | – |
上述图表中,最左边一行表示第一直接依赖范围,最上面一行表示第二直接依赖范围,中间的交叉单元表示传递性依赖范围。
依赖调解
例如,项目A有依赖关系:A->B->C-X(1.0)、A->D->X(2.0)。
依赖调解第一原则是:==最近路径优先==。所以上例中2.0被解析使用。
但是第一原则不能解决所有问题,比如:A->B-Y(1.0)、A->C->Y(2.0)。
路径长度一样的情况下,依赖调解的第二原则:==第一声明优先==。1.0声明在先,所以被解析使用。
optional 依赖是否可选
假设有这样一个依赖关系:A->B、B->X(可选)、B->Y(可选)。根据传递依赖的定义,如果这三个依赖的范围都是compile,那么X、Y就是A的compile范围传递性依赖。然而,由于这里X、Y是可选依赖,依赖将不会得以传递。换句话说,X、Y将不会对A有任何影响。
使用可选依赖特性的原因:可能B实现了两个特性,其中的特性一依赖于X,特性二依赖于Y,而且这两个特性是互斥的,用户不可能同时使用两个特性。比如B是一个持久层隔离工具包,它支持多种数据库,包括MySQL、PostgreSQL等。在构建这个工具包的时候,需要这两种数据库的驱动程序,但在使用这个工具包的时候,只会依赖一种数据库。pom如下:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.juvenxu.mvnbook</groupId>
<artifactId>project-b</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.10</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>8.4-701.jdbc3</version>
<optional>true</optional>
</dependency>
</dependencies>
</project>
使用表示这两个依赖是可选依赖。理想情况下,不应该使用可选依赖。面向对象中,有个单一职责原则,意指一个类应该只有一项职责,而不是糅合太多的功能,这个原则在Maven中也同样使用。
exclusions 排除传递性依赖
如果项目A依赖于项目B,但是由于一些原因,不想引入传递性依赖C,而是自己显示地声明对项目C1.1.0版本的依赖。这个时候使用exclusions声明排除性依赖,exclusions可以包含一个或多个exclusion子元素,因此可以排除一个或多个传递性依赖。
==注意==:声明exclusion的时候只需要groupId和artifactId,不需要version。
归类依赖
如果有同一项目的不同模块(例如Spring Framework),这些依赖的版本都是相同的,而且以后要升级,这些依赖的版本会一起升级,可以写成如下形式:
<project>
...
<properties>
<springframework.version>4.5.2</springframework.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${springframework.version}</version>
<dependency>
...
<dependencies>
</project>
优化依赖
Maven会自动解析所有项目的直接依赖和传递性依赖,并且根据规则正确判断每个依赖的范围,对于一些依赖冲突,也能进行调节,以确保任何一个构件只有唯一的版本在依赖中存在。在这些工作之后,最后得到的那些依赖被称为==已解析依赖(Resolved Dependency)==。可以运行如下命令查看当前项目的已解析依赖:
mvn dependency:list
当前这些依赖经Maven解析后,会构成一个依赖树,通过这颗依赖树就能很清楚地看到某个依赖是通过哪条传递路径引入的。运行如下命令可以查看当前项目的依赖树:
mvn dependency:tree
使用上述两个命令可以帮助我们详细了解项目中所有依赖的具体信息,在此基础上,运行如下命令可以帮助分析当前项目的依赖:
mvn dependency:analyze