1.1 何为Maven坐标
正如之前所说的,Maven的一大功能就是管理项目依赖。为了能自动化地解析任何一个Java构件,Maven就必须将它们唯一标识,这就依赖管理的底层基础——坐标。
1.2 坐标详解
Maven坐标的元素包括:groupId,artifactId、version、packaging、classifier。先看一组坐标定义,如下:
<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项目隶属的实际项目。上例中,groupId为org.sonatype.nexus,org.sonatype表示Sonatype公司创建的一个非营利性组织,nexus表示Nexus这一实际的项目,该groupId与域名nexus.sonatype.org对应,即该groupId表示Sonatype公司的Nexus项目。
- artifactId:该元素定义实际项目中的一个Maven项目(模块),推荐的做法是使用项目名称作为artifactId的前缀。比如上例中的artifactId是nexus-indexer,使用了实际项目名nexus作为前缀,这样可以方便寻找实际构件。之后使用了indexer表示其所属的具体子项目(模块)。
- version:该元素定义了Maven项目所处的版本,如上例中的nexus-indexer的版本是2.0.0。
- packaging:该元素定义Maven项目的打包方式,默认值是jar。打包方式与所生成构件的文件拓展名对应,如上例中的packaging为jar,最终的文件名为nexus-indexer-2.0.0.jar。
- classifier:该元素用来帮助定义构建输出一些附属构件,让附属构建也拥有唯一的坐标。
上述5个元素中,groupId、artifactId、version是必须定义的,packagnig是可选的(默认为jar),而classifier是不能直接定义的。
同时,项目构建的文件名是与坐标相对应的,一般的规则为artifactId-version[-classifier].packaging,其中[-classifier]表示可选。比如上例的nexus-indexer的主构件为nexus-indexer-2.0.0.jar,附属构建有nexus-indexer-2.0.0-javadoc.jar。
1.3 依赖
通过上面的学习,我们配置了一个Maven项目的groupId, artifactId等信息。当我们需要进行开发的时候,我们就需要导入我们这个项目所依赖的一些构建(Jar包)。假设这时候我们需要导入一个用于JUnit测试的依赖,代码如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <groupId>org.sonatype.nexus</groupId> <artifactId>nexus-indexer</artifactId> <version>2.0.0</version> <packaging>jar</packaging> <dependencies> <!--dependency> <groupId>com.chanshuyi.demo</groupId> <artifactId>[the artifact id of the block to be mounted]</artifactId> <version>1.0-SNAPSHOT</version> </dependency--> <!-- JUnit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency> </dependencies> </project>
通过上面我们可以看到,导入依赖是在<dependencies>元素下的<dependency>元素进行配置。通过查询我们可以知道JUnit这个依赖的groupId,artifactId都是:junit,并且我们需要的是4.10版本,依赖的范围为test(即测试范围内可用)。其实一个标准的依赖可以包含的元素有:
<project> ... <dependencies> <dependency> <groupId>...</groupId> <artifactId>...</artifactId> <version>...</version> <type>...</type> <scope>...</scope> <optional>...</optional> <exclusions> <exclusion> .. </exclusion> </exclusions> <dependency> ... ... </project>
其中:
- groupId、artifactId和version:这是依赖的基本坐标,Maven通过坐标找到依赖的构件。你可以通过这里查询构建的这3个元素信息。
- type:依赖的类型,对应于项目坐标定义的packaging。大部分情况下,该元素必须声明,默认值为jar。
- scope:依赖的范围,下文详解。
- optional:标记该依赖是否可选,下文详解。
- exclusions:用来排除传递性依赖,下文详解。
1.4 依赖范围
上面提到,JUnit依赖的测试范围是test,测试范围用元素scope表示。首先需要知道,Maven在编译项目主代码的时候需要使用一套classpath,Maven在编译和执行测试的时候会使用另外一套classpath,实际运行的时候又需要一套classpath。如果在引入依赖的时候指定了scope,那么该依赖只在指定范围内有效。比如上面引入的JUnit的scope为test,则该依赖只在执行测试的时候有效,在运行的时候该依赖是无效的。因此当你尝试在主代码中引入JUnit时,你会发现Junit依赖并不存在。
依赖范围就是用来控制依赖与这三种classpath(编译classpath、测试classpath、运行classpath)的关系,Maven有以下几种依赖范围:
- compile:编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的Maven依赖,对于编译、测试、运行三种classpath都有效。典型的例子是spring-core依赖。
- test:测试依赖范围。只对测试classpath有效,典型的例子是Junit依赖。
- provided:已提供依赖范围。对于编译和测试classpath有效,典型的例子是servlet-api依赖。
- runtime:运行时依赖范围。对于测试和运行有效,典型的例子是JDBC驱动依赖。
- system:系统依赖范围。该依赖与三种classpath的关系,和provided依赖范围完全一致。但是使用system依赖范围的依赖必须通过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(Maven2.0.9及以上):导入依赖范围。了解即可,将在后面详细说到。
[可以补充一个依赖与classpath关系的表格 MARK]
1.5 传递性依赖
1.5.1 何为传递性依赖
我有一个Maven项目account-mail,它有一个compile范围的spring-core依赖,spring-core有一个compile范围的commons-logging依赖,那么commons-logging就会成为account-email的compile范围依赖,commons-logging就是account-email的一个传递性依赖,如图:
1.5.2 传递性依赖和依赖范围
如果A->B->C,即A对于B的依赖范围是compile,B对于C的依赖范围是compile,那么A对于C的依赖范围是compile。此时,我们称A对于B是第一直接依赖,B对于C是第二直接依赖(根据情况的不同可能会有第三、第四……直接依赖),A对于C是传递性依赖。
那么当A对于B是test范围的依赖,B对于C是compile范围的依赖,那么A对于C的依赖是什么范围呢?下面我们通过一个表格来详细讲解:
最左边的一列表示第一直接依赖的范围,最上面的一行表示第二直接依赖的范围,中间的交叉单元格则表示传递性依赖范围的结果。
如果A->B->C的传递性依赖中,如果A->B是compile依赖,B->C是runtime依赖,那么A->C就是runtime范围的依赖。
1.6 依赖调解
1.6.1 第一原则:路径最近者优先
例如在项目中可能有这样的依赖关系:A->B->C->X(1.0),A->D->X(2.0),这个时候X是A的传递性依赖,那我们该取那个为A的依赖呢?根据Maven依赖调解第一原则,我们知道应该取X(2.0)为A的依赖。
1.6.2 第二原则:第一声明者优先
当依赖路径长度相等的前提下,在POM中依赖声明的顺序决定了谁会被解析使用。比如这样的依赖关系:A->B->Y(1.0)、A->C->Y(2.0),这时候Y(1.0)和Y(2.0)的路径长度是一样的,都为2。如果B的依赖声明在C之前,那么Y(1.0)就会被解析使用。
1.7 可选依赖
假设有这样一个依赖关系,项目A依赖与项目B,项目B依赖于项目X和Y,B对于X和Y的依赖都是可选依赖:A->B、B->X(可选)、B->Y(可选),那么X、Y就是A的传递性依赖。然而,由于这里X、Y是可选依赖,依赖将不会得以传递。比如B是一个持久层隔离工具包,它支持多种数据库,包括MySQL、PostgreSQL等,在构建这个工具包的时候,需要这两种数据库的驱动程序,但在使用这个工具包的时候,只会依赖一种数据库。这时候MySQL、PostgreSQL这两个依赖对于B就是可选的。此时项目B的依赖声明代码清单如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>1.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>1.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>
在理想情况下,是不应该使用可选依赖的。因为在面向对象设计中,有单一职责原则,意指一个类只有一项职责。应用到上面的例子中也就是说在com.juvenxu.mvnbook.project-b项目中不应该实现了两个功能。最好的做法是为MySQL和PostgreSQL分别创建一个Maven项目,基于同样的groupId分配不同的artifactId,如:com.juvenxu.mvnbook:project-b-mysql和com.juvenxu.mvnbook:project-b-postgresql,在com.juvenxu.mvnbook:project-b-mysql实现对MySQL数据的持久化,在com.juvenxu.mvnbook:project-b-postgresql实现对postgresql数据库的持久化。
1.8 排除依赖
有时候会出现这样的情况,你的Hibernate依赖于Sun JTA API,但是因为版权原因,Sun JTA API并不在仓库中。而Apache Geronimo项目有一个对应的实现。这时你就可以排除Sun JAT API,再声明Geronimo的JTA API实现,见代码清单:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.juvenxu.mvnbook</groupId> <artifactId>project-a</artifactId> <version>1.0.0</version> <dependencies> <dependency> <groupId>com.juvenxu.mvnbook</groupId> <artifactId>project-b</artifactId> <version>1.0.0</version> <exclusions> <exclusion> <!-- 排除对project-c的依赖 --> <groupId>com.juvenxu.mvnbook</groupId> <artifactId>project-c</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.juvenxu.mvnbook</groupId> <artifactId>project-c</artifactId> <version>1.0.0</version> </dependency> </dependencies> </project>
上述代码中,项目A依赖于项目B,但是由于一些原因,不想引入传递性依赖C,而是自己显示声明对于项目C1.0.0版本的依赖。代码中用exclusions元素可以包含多个exclusion子元素。需要注意的是声明exslusion的时候只需要groupId和artifactId,而不需要version元素。
1.9 归类依赖
当我们使用Spring Framework的依赖时,会有许多依赖,如:spring-core:2.5.6.org、org.springframework:spring-beans:2.5.6等,它们是来自同一项目下的不同模块。它们依赖的版本都是相同时,当需要升级Spring Framework时,这些依赖的版本都会一起升级。这时候我们可以用Maven属性的方式来定义一个名为springframework.version的属性,让所有的spring framework子模块都引用它。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.juvenxu.mvnbook</groupId> <artifactId>project-a</artifactId> <version>1.0.0</version> <properties> <springframework.version>3.1.2.RELEASE</springframework.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${springframework.version}</version> </dependency> </dependencies> </project>
这样当我们需要升级Spring Framework的时候就只需要改属性值就可以了。
1.10 优化依赖
使用mvn dependency:list命令可以查看项目已解析的依赖
使用mvn dependency:tree命令可以查看项目构成的依赖树
使用mvn dependency:analyze命令可以分析当前项目的依赖
下一篇:暂无