提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
目录
1. Maven依赖调解(Dependency Mediation)的第一原则是:路径最近者优先。
一、maven坐标
1. groupId
groupid是定义当前Maven项目隶属的实际项目。
比如SpringFramework这一实际项目,其对应的Maven项目会有很多,如spring-core、spring-context等。一个实际项目往往会被划分成很多Maven项目(模块)。
groupId的表示方式与Java包名的表示方式类似,通常与域名反向一一对应。例如groupId为org.sonatype.nexus, org.sonatype表示Sonatype公司建立的一个非盈利性组织,nexus表示Nexus这一实际项目,该groupId与域名nexus.sonatype.org对应。
2. artifactId
artifactId是定义实际项目中的一个Maven项目(模块),推荐的做法是使用实际项目名称作为artifactId的前缀。
例如artifactId是nexus-indexer,使用了实际项目名nexus作为前缀,这样做的好处是方便寻找实际构件。
二、依赖范围
1. compile
编译依赖范围。默认设置。对于编译、测试、运行三种classpath都有效。
2. test
测试依赖范围。只对于测试classpath有效,在编译主代码或者运行项目的使用时将无法使用此类依赖。例如JUnit。
3. provided
已提供依赖范围。对于编译和测试classpath有效,但在运行时无效。例如servlet-api,在运行时,由于容器已经提供,不需要重复引入。
4. runtime
运行时依赖范围。对于测试和运行classpath有效,但在编译时无效。例如JDBC驱动实现,编译时只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。
5. system
系统依赖范围。该依赖与三种classpath的关系,和provided依赖范围完全一致。但是,使用system范围的依赖时必须通过systemPath元素显式地指定依赖文件的路径。由于此类依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能造成构建的不可移植,因此应该谨慎使用。
6. import
导入依赖范围。
三、传递性依赖
假设A依赖于B,B依赖于C,我们说A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖。第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围,如表所示,最左边一列表示第一直接依赖范围,最上面一行表示第二直接依赖范围,中间的交叉单元格则表示传递性依赖范围。
四、依赖调解
1. Maven依赖调解(Dependency Mediation)的第一原则是:路径最近者优先。
例如,项目A有这样的依赖关系:A->B->C->X(1.0)、A->D->X(2.0),其中X(1.0)的路径长度为3,而X(2.0)的路径长度为2,因此X(2.0)会被解析使用。
2. 依赖调解的第二原则:第一声明者优先。
在依赖路径长度相等的前提下,在POM中依赖声明的顺序决定了谁会被解析使用,顺序最靠前的那个依赖优胜。
比如这样的依赖关系:A->B->Y(1.0)、A->C->Y(2.0),Y(1.0)和Y(2.0)的依赖路径长度是一样的,都为2。如果B的依赖声明在C之前,那么Y(1.0)就会被解析使用。
五、依赖优化
Maven会自动解析所有项目的直接依赖和传递性依赖,并且根据规则正确判断每个依赖的范围,对于一些依赖冲突,也能进行调节,以确保任何一个构件只有唯一的版本在依赖中存在。
查看当前项目的已解析依赖:mvn dependency:list
查看当前项目的依赖树:mvn dependency:tree
帮助分析当前项目的依赖: mvn dependency:analyze。结果中重要的有两个部分。
首先是Used undeclared dependencies,意指项目中使用到的,但是没有显式声明的依赖。这种依赖意味着潜在的风险,当前项目直接在使用它们,例如有很多相关的Java import声明,而这种依赖是通过直接依赖传递进来的,当升级直接依赖的时候,相关传递性依赖的版本也可能发生变化,这种变化不易察觉,但是有可能导致当前项目出错。显式声明任何项目中直接用到的依赖。
还有一个重要的部分是Unused declared dependencies,意指项目中未使用的,但显式声明的依赖。需要注意的是,对于这样一类依赖,我们不应该简单地直接删除其声明,而是应该仔细分析。由于dependency:analyze只会分析编译主代码和测试代码需要用到的依赖,一些执行测试和运行时需要的依赖它就发现不了。
六、依赖管理
Maven提供的dependencyManagement元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性。在dependencyManagement元素下的依赖声明不会引入实际的依赖,不过它能够约束dependencies下的依赖使用。
完整的依赖声明已经包含在父POM中,子模块只需要配置简单的groupId和artifactId就能获得对应的依赖信息,从而引入正确的依赖。
使用这种依赖管理机制似乎不能减少太多的POM配置,不过还是强烈推荐采用这种方法。其主要原因在于在父POM中使用dependencyManagement声明依赖能够统一项目范围中依赖的版本,当依赖版本在父POM中声明之后,子模块在使用依赖的时候就无须声明版本,也就不会发生多个子模块使用依赖版本不一致的情况。这可以帮助降低依赖冲突的几率。
想要在另外一个模块中使用与代码清单8-14完全一样的dependencyManagement配置,除了复制配置或者继承这两种方式之外,还可以使用import范围依赖将这一配置导入:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.juvenxu.mvnbook.account</groupId>
<artifactId>account-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
七、版本管理
1. 快照版本和发布版本
为了方便团队的合作,在项目开发的过程中,大家都应该使用快照版本,Maven能够很智能地处理这种特殊的版本,解析项目各个模块最新的“快照”。快照版本机制促进团队内部的交流。
项目不应该依赖于任何组织外部的快照版本依赖,由于快照版本的不稳定性,这样的依赖会造成潜在的危险。也就是说,即使项目构建今天是成功的,由于外部的快照版本依赖实际对应的构件随时可能变化,项目的构建就可能由于这些外部的不受控制的因素而失败。
当项目需要对外发布时,我们显然需要提供非常稳定的版本,使用该版本应当永远只能够定位到唯一的构件,而不是像快照版本那样,定位的构件随时可能发生变化。
由于快照对应了项目的开发过程,因此往往对应了很长的时间,而正式版本对应了项目的发布,因此仅仅代表某个时刻项目的状态。
发布版本应当满足以下条件:
所有自动化测试应当全部通过。
项目没有配置任何快照版本的依赖。快照版本的依赖意味着不同时间的构建可能会引入不同内容的依赖,这显然不能保证多次构建能够生成同样的结果。
项目没有配置任何快照版本的插件。
2. 版本号约定
<主版本>.<次版本>.<增量版本>-<里程碑版本>
主版本:表示了项目的重大架构变更。例如,Maven 2和Maven 1相去甚远;Struts 1和Struts 2采用了不同的架构;JUnit 4较JUnit 3增加了标注支持。
次版本:表示较大范围的功能增加和变化,及Bug修复。例如Nexus 1.5较1.4添加了LDAP的支持,并修复了很多Bug,但从总体架构来说,没有什么变化。
增量版本:一般表示重大Bug的修复。例如项目发布了1.4.0版本之后,发现了一个影响功能的重大Bug,则应该快速发布一个修复了Bug的1.4.1版本。
里程碑版本:顾名思义,这往往指某一个版本的里程碑。例如,Maven 3已经发布了很多里程碑版本,如3.0-alpha-1、3.0-alpha-2、3.0-beta-1等。这样的版本与正式的3.0相比,往往表示不是非常稳定,还需要很多测试。
不是每个版本号都必须拥有这四个部分。一般来说,主版本和次版本都会声明,但增量版本和里程碑就不一定了。
3. 主干、标签与分支
主干:项目开发代码的主体,是从项目开始直到当前都处于活动的状态。从这里可以获得项目最新的源代码以及几乎所有的变更历史。
分支:从主干的某个点分离出来的代码拷贝,通常可以在不影响主干的前提下在这里进行重大Bug的修复,或者做一些实验性质的开发。如果分支达到了预期的目的,通常发生在这里的变更会被合并(merge)到主干中。
标签:用来标识主干或者分支的某个点的状态,以代表项目的某个稳定状态,这通常就是版本发布时的状态。