Maven简介
Maven是什么
Maven是Apache组织的开源项目,Maven是一个跨平台项目管理工具,Maven主要服务于基于Java平台的项目构建、依赖管理、项目信息管理。
什么是构建(build)
构建(build)包括编译、运行单元测试、生成文档、打包和部署等繁琐而不起眼的工作。将构建自动化能够大大节省时间。
Maven是优秀的构建工具
- Maven是一个功能强大的构建工具,它能帮我们自动化构建过程,用于清理、编译、测试到生成报告再到打包和部署。
- Maven最大化消除了构建的重复,抽象了一个完整的构建生命周期模型,并且为绝大部分的构建任务提供了已实现的插件,可以标准化构建过程,使用Maven构建后,所有项目的构建命令都是简单一致的,极大的降低了学习升本,有利于项目团队的标准化。
- Maven跨平台,Windows/Linux/MacOS
总之,Maven不仅能帮我们自动化构建,还能抽象构建过程,提供构建任务实现,跨平台对外提供了一致的操作接口。
Maven是优秀的依赖管理和项目信息管理工具
- Maven提供中央仓库,帮助我们自动下载构建
- Maven通过一个坐标系统准确地定位每一个构建(artfact),通过一组坐标能够找到任何一个Java类库文件。
- Maven对于项目目录结构、测试用例命名方式都有既定的规则,只要遵循这些规则,用户在项目间切换免去了额外的学习升本。约定优于配置。
坐标与依赖
坐标
先看一组坐标:
4.0.0 com.eric eric-parent 2.0-SNAPSHOT jar
- groupId:定义当前Maven项目隶属的实际项目。
- artifactId:定义实际项目中的一个Maven项目(模块)。推荐使用实际项目名称作为artifactId的前缀。
- version:定义Maven项目当前所处的版本。
- packageing:定义Maven项目的打包方式。不填写默认是jar
- classifier:该元素用来帮助定义构建输出的一些附属构建。附属构建与主构件对应。上例子中主构件是eric-parent-2.0-SNAPSHOT.jar。项目有可能会通过使用一些插件生成如eric-parent-2.0-SNAPSHOT-javadoc.jar、eric-parent-2.0-SNAPSHOT-sources/.jar这样一些附属构建,其中包含了Java文档与源代码。这个时候,javadoc和sources就是这两个附属构建的classifier。这样,附属构建也有了自己的唯一坐标。注意:不能直接定义项目的classifier,因为附属构件不是项目直接默认生成的,而是由附加的插件帮助生成的。
依赖
先看一组依赖:
javax.servlet.jsp javax.servlet.jsp-api 2.3.1 provided
- groupId:定义当前Maven项目隶属的实际项目。
- artifactId:定义实际项目中的一个Maven项目(模块)。推荐使用实际项目名称作为artifactId的前缀。
- version:定义Maven项目当前所处的版本。
- type:依赖的类型,对应于项目坐标定义的packaging。大部分情况下不需要声明,默认是jar。
- scope:依赖的范围
- optional:标记依赖是否可选
- exclusions:排除依赖的传递性
scope依赖范围
依赖范围用来控制依赖与三种classpath(编译classpath、测试classpath、运行classpath)的关系。
-
compile:编译依赖范围,默认的依赖范围,对于编译、测试、运行三种classpath都有效。
-
test:测试依赖范围。只对于测试classpath有效,在编译主代码或者运行项目的使用时无法使用此类依赖。典型的例子是JUnit,只在编译测试代码以及运行测试的时候才需要。
-
provided:已提供依赖范围,对于编译和测试classpath有效,但是在运行时无效。典型的例子就是servlet-api,编译和测试项目的时候需要该依赖,但是运行项目的时候,容器提供,就不需要重复引入。
-
runtime:运行时依赖范围,对于测试和运行classpath有效。但是在编译主代码时无效。典型的例子就是JDBC驱动。项目主代码编译只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。
-
system:系统依赖范围。和provided依赖范围一致,但是使用system必须通过systemPath元素显式地指定依赖文件的路径。由于此类依赖不是通过Maven仓库解析的,而且与本机系统绑定,可能造成构建的不可移植。
javax.sql jdbc-stdext 2.0 system ${java.home}/lib/rt.jar -
import:导入依赖范围。该依赖范围不会对三种classPath产生实际影响,参照第八章。
传递性依赖和依赖范围
A依赖B,B依赖C,我们说A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖。
第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围。
第二依赖 | compile | test | provided | runtime | |
---|---|---|---|---|---|
第一依赖 | - | - | - | - | - |
compile | - | compile | - | - | runtime |
test | - | test | - | - | test |
provided | - | provided | - | provided | provided |
runtime | - | runtime | - | - | runtime |
规律:
- 当第二直接依赖的范围是compile时,传递性依赖和第一直接依赖一致。
- 当第二直接依赖的范围是test时,依赖不会传递。
- 当第二直接依赖的范围是provided的时候,值传递第一直接依赖为provided的依赖
- 当第二直接依赖的范围是runtime时,除了compile变为runtime,其他传递性依赖和第一直接依赖一致。
依赖调解
Maven引入的传递性依赖机制,一方面大大简化和方便了依赖声明,另一方面,大部分情况下我们只需要关心项目的直接依赖是什么,而不用考虑这些直接依赖会引入什么传递性依赖。但有时候,当传递性依赖造成问题的时候,我们就需要清楚的知道该传递性依赖是从哪条依赖路径引入的。
例如,项目A有这样的依赖关系:A->B->C->X(1.0)、A->D->X(2.0),X是A的传递性依赖,但是两条依赖路径上有两个版本的X,那么哪个X会被Maven解析使用呢?两个版本都解析显然是不对的,因为会造成重复依赖,因此必须选择一个。Maven依赖调解的第一原则是:路径最近者优先,该例中X(1.0)的路径长度为3,而X(2.0)的路径长度为2,因此X(2.0)会被解析使用。
依赖调节第一原则不能解决所有的问题,如果两条依赖路径的长度是一样的呢?那到底谁被解析使用呢。Maven的依赖调解的第二原则:第一声明者优先。在依赖路径相等的情况下,在POM中依赖声明的顺序决定了谁会先被解析使用,顺序在前的先解析使用。
只会对当前项目产生影响,当其他项目依赖于该项目的时候,这个依赖不会被传递。
最佳实践
排除依赖
传递性依赖可能会使得当前项目引入不稳定版本,需要。
归类依赖
将部分依赖的版本进行统一管理,类似JAVA中的常量定义与使用。
优化依赖
去除多余的依赖,显示地声明某些必要的依赖。
仓库
在Maven的术语中,仓库是一个位置(place)。Maven仓库是项目中依赖的第三方库,这个库所在的位置叫做仓库。在Maven中,任何一个依赖、插件或者项目构建的输出,都可以称之为构件。Maven仓库能帮助我们管理构件(主要是JAR),它就是放置所有JAR文件(WAR,ZIP,POM等等)的地方。
图-Maven仓库分类
中央仓库是Maven核心自带的远程仓库,它包含了绝大部分开源的构件。私服是另一种特殊的远程仓库,为了节省带宽和时间,应该在局域网内架设一个私有的服务器,用其代理所有外部的远程仓库,内部项目还能部署到私服上供其它项目使用。Maven根据(从仓库解析依赖的机制)从仓库解析并使用依赖构件。
部署到远程仓库,采用命令mvn clean deploy,如果当前版本是快照版本,就会部署到快照仓库,如果当前版本是发行版本,就会部署到发布版本仓库。当项目经过完善的测试后需要发布的时候,就应该将快照版本更改为发布版本。快照版本只应该在组织内部的项目或者模块间依赖使用,因为这时,组织对于这些快照的依赖具有完全理解以及控制权。项目不应该依赖任何组织外部的快照版本,因为不稳定。
生命周期和插件
Maven的生命周期就是为了对所有的构建过程进行抽象和统一,Maven从大量项目和构建工具中学习和反思,总结了一套高度完善的、易扩展的生命周期,这个生命周期包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有的构建步骤。也就是说,几乎所有的项目构建,都能映射到这样一个生命周期上。
生命周期是抽象的,实际的任务都由相应的插件来完成,类似模板方法的设计模式。Maven定义的生命周期和插件机制一方面保证了所有Maven项目有一致的构建标准,另一方面又通过默认插件简化和稳定了实际项目的构建。此外插件还提供了足够的扩展空间,用户可以通过配置现有插件或者自行编写插件来自定义构建行为。
Maven有三套相互独立的生命周期,分别是clean、default和site。clean生命周期的目的是清理项目,default生命周期的目的是构建项目,而site生命周期的目的是建立项目站点。
clean生命周期
clean生命周期目的是清理项目,三个阶段:
- per-clean:执行一些清理前需要完成的工作。
- clean:清理上一次构建生成的文件。
- post-clean执行一些清理后需要完成的工作。
default生命周期
default生命周期定义了真正构建时,所需要执行的所有步骤,它是所有生命周期中最核心的部分,default生命周期是最核心的,它包含了构建项目时真正需要执行的所有步骤。
- validate
- initialize
- generate-sources
- process-sources
- generate-resources
- process-resources :处理项目主资源文件。一般是对src/main/resources目录的内容进行变量替换后,复制到target目录,准备打包;
- compile :编译项目的源代码,编译src/main/resources目录的内容至项目输出的主classpath目录中;
- process-classes
- generate-test-sources
- process-test-sources:处理项目测试资源文件
- generate-test-resources
- process-test-resources
- test-compile :编译测试源代码
- process-test-classes
- test :运行测试代码,测试代码不会被打包或部署;
- prepare-package
- package :打包成jar或者war或其它可发布的格式;
- pre-integration-test
- integration-test
- post-integration-test
- verify
- install :将打好的包安装到本地仓库,供其他项目使用;
- deploy :将打好的包安装到远程仓库,供其他项目使用;
site生命周期
site生命周期的目的是建立和发布项目站点。Maven能够基于POM所包含的信息,自动生成一个友好的站点,方便团队交流和发布项目信息。
- pre-site
- site :生成项目的站点文档;
- post-site
- site-deploy :将生成的项目站点发布到服务器上
命令行与生命周期
从命令行执行Maven任务最主要的方式就是调用Maven的生命周期阶段。需要注意的是,各个生命周期是相互独立的,而一个生命周期的阶段是后前后依赖关系的。
- mvn clean : clean生命周期的clean阶段,执行clean生命周期的pre-clean和clean阶段。
- mvn test:该命令调用default生命周期的test阶段。实际执行的阶段为default生命周期的validate、initialize等,直到test的所有阶段,这解释了为什么在执行测试的时候,项目代码能够自动得以编译。
- mvn clean install: 实际执行clean生命周期的pre-clean和clean以及default生命周期的validate到install所有阶段。结合两个生命周期,在执行真正的项目构建之前清理项目是一个很好的实践。
- mvn clean deploy site-deploy: clean生命周期的pre-clean和clean,default生命周期的所有阶段,以及site生命周期的所有阶段。
聚合与继承
反应堆
在一个多模块的Maven项目中,反应堆(Reactor)是指所有模块组成的一个构建结构,对于单模块项目,反应堆就是该模块本身,对于多模块项目来说,反应堆就包含了各模块之间的依赖于继承关系,从而能够自动计算出合理的模块构建顺序。
构建顺序,无依赖的按pom的读取顺序;有依赖的优先构建较深层次的依赖。
裁剪反应堆,用户仅仅构建完整反应堆中的某些个模块,即实时地。