简介
什么是Maven
maven这个单词的翻译为“专家”或“内行”。作为我们开发过程中使用的工具而言,Maven是一个跨平台的项目管理工具,并且是Apache组织中的开源项目之一,主要用于Java平台的项目构建,依赖管理和项目信息管理等。它是一个异常强大的构建工具,能够帮助我们自动化构建过程,从清理,编译,测试到生产报告,再到打包和部署,我们只需要使用maven配置好项目,然后输入简单的命令,maven会帮我们处理这些繁琐的任务。
maven是跨平台的,这意味着无论是windows上还是在linux,mac上的项目都可以使用同样的命令。同时,maven不仅仅是构建工具,还是一个依赖管理工具和项目信息管理个工具,可以帮助我们自动下载项目所需要的构件。
maven对于项目目录结构,测试用例命名方法等内容都有既定的规则,只要遵循了这些成熟的规则,我们在不同的项目切换的时候就免去了额外的学习成本,上手就容易许多,可以说是约定优于配置(Convention over Configuration)。
Maven的安装(Ubantu)
检查系统的java环境变量配置是否成功
qiu@qiu-qiu:~$ echo $JAVA_HOME
qiu@qiu-qiu:~$ java -version
java环境配置成功后,下载maven:http://maven.apache.org/download.cgi
配置maven的环境变量:
解压安装包后,到解压出来的maven文件的目录下创建一个符号链接,方便更新:
qiu@qiu-qiu:~$ ln -s apache-maven-3.5.0 apache-maven
设置M2_HOME变量指向这个符号链接,然后将符号链接中的bin目录添加进系统的PATH变量:
qiu@qiu-qiu:~$ sudo vi /etc/profile
添加以下几行:
export M2_HOME=/home/qiu/maven/apache-maven
export PATH=$PATH:$M2_HOME/bin
最后更新配置文件:
qiu@qiu-qiu:~$ source /etc/profile
检查是否安装成功
qiu@qiu-qiu:~$ mvn -v
输出:
Apache Maven 3.5.0 (ff8f5e7444045639af65f6095c62210b5713f426; 2017-04-04T03:39:06+08:00)
Maven home: /home/qiu/maven/apache-maven
Java version: 1.8.0_131, vendor: Oracle Corporation
Java home: /usr/jdk1.8.0_131/jre
Default locale: zh_CN, platform encoding: UTF-8
OS name: “linux”, version: “4.8.0-49-generic”, arch: “amd64”, family: “unix”
关于maven的升级:由于我们之前平行地创建了一个符号链接,然后在配置环境变量的时候使用的是这个符号链接,于是在升级的时候就可以利用这个符号链接了。我们只需要将符号链接指向新的maven版本即可。
在IDEA(版本:2017.1.2)开发环境中搭建maven:
在Settings—Build,Execution,Deployment—Build Tools—Maven中设置Maven home directory; User settings file; Local repository即可。
maven的使用
POM文件
maven项目的核心是pom.xml。POM(Project Object Model,项目对象模型)定义了项目的基本信息,用于描述项目是如何构建的,声明项目依赖,等等。
pom.xml文件里面元素介绍:
首先是XML头,指定了该xml文件的版本以及编码方式。紧接着是project元素,project元素是所有pom.xml的根元素,它还声明了一些POM相关的命名空间以及xsd元素。根元素下的第一个子元素是modelVersion指定了当前POM模型的版本。接下来介绍3个重要的元素标签:
groupId,artifactId,version。这3个元素定义了一个项目的基本坐标,在maven世界中,任何的jar,pom或者war都是以基于这些基本的坐标进行区分的。
groupId定义了项目属于哪个组,这个组往往和项目所在的组织有关。artifactId定义了当前maven项目在组中唯一的ID。version,顾名思义,指定了当前项目的版本号。最后还可以为项目设定name元素,声明一个对于用户更为友好的项目名称。
dependencies元素声明项目的依赖(java代码中的import),该元素下可以包含多个dependency元素,里面包含所依赖的构件的mavan坐标。
没有任何实际的java代码,我们就能够定义一个maven项目的POM,这体现了maven的一大优点,它能让项目对象模型最大程度地与实际代码相独立,也就是解耦。这样也避免了java代码与POM代码的相互影响。比如以后当项目需要升级版本的时候,我们只需要修改POM即可。
主代码以及测试代码
项目的主代码和测试代码不用在于,主代码会被打包到最终的构件中(如jar),而测试代码只是在运行测试时候用到,不会被打包。
一般来说,项目中的java类的包(package)都应该基于项目的groupId和artifactId,这样更加清晰,更加符合逻辑,也方便搜索构件或者java类。
主代码位于:/src/main/java目录下;测试代码位于:/src/test/java目录下;
mvn命令
mvn clean :清除maven的输出目录target
mvn compile :编译项目主代码,将项目主代码编译至target/classes目录中
mvn test-compile :编译测试的内容
mvn test :运行测试(在运行测试之前会自动执行项目主资源处理,主代码编译,测试资源
处理,测试代码编译等工作)
mvn package :生成target目录,编译、测试代码,生成测试报告,生成jar/war文件;默认是jar文件,只打包主项目代码。
mvn install :在本地Repository中安装jar
mvn archetype:generate :创建mvn项目(使用Archetype生成项目骨架)
mvn dependency:list :查看当前项目的已解析依赖
mvn dependency:tree :查看当前项目的依赖树(传递依赖的路径)
mvn dependency:analyze :帮助分析当前项目的依赖
maven坐标
为了能够自动化解析任何一个java构件,maven就必须将他们唯一标识,这就依赖管理的底层基础--坐标。maven定义了这样一组规则:世界上任何一个构件都可以使用maven坐标唯一标识,maven坐标的元素包括groupId,artifactId,version,packaging,classifier。
坐标元素介绍:
groupId:定义当前maven项目隶属的实际项目
artifactId:定义实际项目中的一个maven项目(模块)
version:定义maven项目的当前所处的版本
packaging:定义maven项目的打包方式,默认的打包方式是jar
classifier:用来帮助定义构建输出的一些附属构件。不能直接定义项目的classifier,因为附属构件不是项目默认生成的,而是由附件的插件帮助生成的
maven依赖
根元素project下的dependencies可以包含一个或者多个dependency元素,以声明一个或者多个项目依赖。每个依赖可以包含的元素有:
groupId,artifactId和version:依赖的基本坐标,对于任何一个依赖来说,基本坐标是最重要的,maven根据坐标才能找到需要的依赖。
type:依赖的类型,对应于项目坐标定义的packaging。大部分情况下,该元素不必声明,默认为jar
scope:依赖的范围
optional:标记依赖是否可选
exclusions:用来排除传递性依赖
依赖的范围
依赖范围就是用来控制依赖与三种classpath(编译classpath,测试classpath,运行classpath)的关系:
compile:编译依赖范围。如果没有指定,就会默认是该范围。此范围的依赖对编译,测试,运行都有效。
test:测试依赖范围。只在测试是有效,在编译主代码或者运行项目的时候无法使用此类依赖。
provided:已提供依赖范围。对于编译和测试有效,但在运行时候无效。
runtime:运行时依赖范围。对于测试和运行时有效,但在编译主代码时无效。
依赖的传递性
在使用其他依赖的构件的时候,不用去考虑它依赖了什么,也不用担心引入多余的依赖。maven会解析各个直接依赖的POM,将那些必要的间接依赖以传递性的形式引入到当前的项目中。
maven引入的传递性依赖机制,一方面大大简化和方便了依赖声明,另一方面,大部分情况下我们只需要关心项目的直接依赖是什么,而不用去考虑这些直接依赖会引入声明传递性依赖。
依赖的调解
比如,项目A有这样的依赖关系:A–B–C–X(1.0),A–D–X(2.0),这个时候X是A的传递性依赖。会选择哪个呢?根据maven的第一原则:路径最近者优先。选择X(2.0);
那么,如果路径相同的呢?这时就用到了第二原则:第一声明者优先。也就是说,在依赖路径长度相等的时候,在POM中的依赖声明的顺序决定了谁会被使用。
排除依赖
传递性依赖会给项目隐式地引入很多依赖,这极大地简化了项目依赖的管理,但是由于一些原因,不想引入某些传递性依赖。这个时候我们可以使用exclusions标签声明排除依赖,exclusions可以包含一个或者多个exclusion子元素,可以排除一个或者多个传递性依赖。
maven仓库
maven仓库简介
坐标和依赖是任何一个构件在maven世界中的逻辑表示方式,而构件的物理表示方式是文件,maven通过仓库来统一管理这些文件。实际的maven项目将不再各自存储其依赖文件,它们只需要声明这些依赖的坐标,在需要的时候,maven会自动根据坐标找到仓库中的构件,并使用它们。
maven仓库分类
对于maven来说,仓库分为2大类:本地仓库与远程仓库。当maven根据坐标寻找构件的时候,它首先会查看本地仓库,如果本地仓库存在此构件,则直接使用。如果本地仓库不存在此构件,或者需要查看是否有更新的构件,maven就会去远程仓库查找,发现需要的构件之后,下载到本地仓库再使用。如果本地仓库与远程仓库都没有,则报错。
这里,需要说明私服。私服是一种特殊的远程仓库,为了节省带宽和时间,应该在局域网内架设一个私有的仓库服务器,用其代理所有外部的远程仓库。
如果默认的中央仓库无法满足项目的需求,可能项目需要的构件存在于另一个远程仓库中。这个时候可以在项目的POM文件中,repositories元素下使用repository子元素声明一个或者多个远程仓库。我们需要注意的是,maven默认的中央仓库的id是central。
依赖坐标搜索
使用maven进行日常开发的时候,一个常见的问题就是如何寻找需要的依赖,我们可以通过需要使用的类库的名字来找到maven依赖所需的坐标。
我们可以直接去maven的官方网站的中央仓库搜索也可以通过其他的搜索服务,比如:
地址:http://repository.sonatype.org/
Nexus是当前流行的开源maven仓库管理软件,提供了关键字搜索,类名搜索,坐标搜索等功能。
生命周期与插件
在maven的日常使用中,命令行的输入往往就对应了生命周期,如mvn package就表示执行默认生命周期阶段package。maven的生命周期是抽象的,其实际行为都由插件来完成,如package阶段的任务可能就就会由maven-jar-plugin完成。
maven的生命周期就是为了对所有的构建过程进行抽象统一,包含了项目的清理,初始化,编译,测试,打包,集成测试,验证,部署和站点生成等几乎所有的构建步骤。所有的项目构建都能够映射到这样一个生命周期上。在maven的设计中,实际的任务(如编译源代码)都交由插件了完成。
maven定义的生命周期和插件机制一方面保证了所有maven项目有一致的构建标准,另一方面又通过默认插件简化和稳定了实际项目的构建。此外,该机制还提供了足够的扩展空间,用户可以通过配置现有的插件或者自行编写插件来自定义构建行为。
三套生命周期
maven拥有三套互相独立的生命周期,分别是clean,default和site。需要注意的是,生命周期之间是互相独立的,生命周期中的各个阶段是前后依赖的。
clean生命周期
clean生命周期的目的是清理项目,它包含三个阶段:
pre-clean 执行一些清理前需要完成的工作
clean 清理上一次构建生成的文件
post-clean 执行一些清理后需要完成的工作
default生命周期
default生命周期定义了真正构建时所需要执行的所有步骤,它是所有生命周期中最核心的部分。
这里只列举一些常见的阶段:(按照先后顺序)
compile:编译项目的主源码。一般来说,是编译src/main/java目录下的java文件至项目输出的主classpath目录中
test-compile:编译项目的测试代码。一般来说,是编译src/test/java目录下的java文件至项目输出的主classpath目录中
test:使用单元测试框架运行测试,测试代码不会被打包或部署
package:接受编译好的代码,打包成可发布的格式,如jar
install:将包安装到maven本地仓库
deploy:将最终的包复制到远程仓库
site生命周期
site生命周期的目的是建立和发布项目站点,maven能够基于POM所包含的信息,自动生成一格友好的站点,方便团队交流和发布项目信息。
pre-site:执行一些在生成项目站点之前需要完成的工作
site:生成项目站点文档
post-site:执行一些在生成项目站点之后需要完成的工作
site-deploy:将生成的项目站点发布到服务器上
插件目标
我们知道,maven的核心仅仅定义了抽象的生命周期,具体的任务是交由插件完成的,插件以独立的构件形式存在。对于插件本身,为了能够复用代码,它往往能够完成多个任务,因此这些功能聚集在一个插件里,每个功能就是一个插件目标。
maven的生命周期与插件互相绑定,用以完成实际的构建任务。具体而言,是生命周期的阶段与插件的目标互相绑定,以完成某个具体的构建任务。
几乎所有maven插件的目标都有一些可配置的参数,可以通过POM文件的配置来调整参数。关于插件的文档信息,我们可以参考Apache的maven官方网站上去获取。
聚合与继承
在这个技术飞速发展的时代,各类用户对软件的要求越来越高,软件本身也变得越来越复杂。因此,软件设计人员往往会采用各种方式对软件划分模块,以得到更清晰的设计以及更高的重用性。当把maven应用到实际项目中的时候,也需要将项目分成不用的模块。maven的聚合特性能够把项目的各个模块聚合在一起构建,而maven的继承特性则能够帮助抽取各个模块中相同的 依赖和插件等配置,在简化POM的同时,还能促进各个模块配置的一致性。
聚合
当我们希望一次性构建多个项目,而不是到多个模块的目录下分别运行mvn命令。这个时候,就需要maven的聚合。
为了能够使用一条命令就能构建多个模块,我们需要创建一个额外的模块(聚合模块),然后通过该模块构建整个项目的所有模块。额外的聚合模块的POM特殊在于:
packaging标签的值为:POM。对于非聚合模块,他们的packaging默认为jar。
modules元素,这是实现聚合的核心配置。我们可以通过在一个打包方式为POM的maven项目中声明任意数量的module元素(需要被聚合的模块)来实现模块的聚合,每一个module的值都是一个当前的POM的相对目录(需要被聚合的模块的目录相对与当前聚合模块POM的相对目录)。
为了方便构建,通常将聚合模块放在项目目录的最顶层,其他模块则作为聚合模块的子目录存在。
继承
maven里面的继承,准确的说是POM文件的继承。我们可以创建POM文件的父子结构,然后在父POM中声明一些配置提供给子POM继承,以实现”一处声明,多处使用”的目的。父POM的packaging为pom,这一点与聚合模块一样。
由于父模块只是为了帮助消除配置的重复,因此它本身不包含除POM之外的项目文件。
有了父模块,就需要让其他模块来继承它。子模块的POM中,使用parent元素来声明父模块,parent下的子元素groupId,artifactId,version指定了父模块的坐标,元素relativePath表示父模块POM的相对路径。当项目构建时,maven首先会根据ralativePath检查父模块的POM,如果找不到,再从本地仓库中查找。relativePath的默认值是 ../pom.xml 也就是说,maven默认父POM在上一层目录中。
依赖管理
在POM文件中,依赖是可以被继承的。maven提供dependencyManagement元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活。在dependencyManagement元素下的依赖声明不会引入实际的依赖,不过它能够约束dependencies下依赖的使用。使用dependencyManagement声明的依赖,不会给子模块引入依赖,但是可以被继承。在子模块中只需要声明依赖的使用,如果子模块不声明依赖的使用,即使该依赖已经在父POM的dependencyManagement中声明了,也不会产生任何实际的效果。
在dependencyManagement元素中有import依赖范围,使用该范围的依赖通常指向一个POM,作用是将目标POM中的dependencyManagement配置导入并合并到当前POM的dependencyManagement元素中。这里import表示导入配置的意思。
插件管理
类似地,maven也提供了pluginManagement元素帮助管理插件,在该元素下配置的依赖不会造成实际的插件调用行为,当POM中配置了真正的plugin元素,并且其groupId,artifactId与pluginManagement中的插件匹配时,pluginManagement的配置才会影响实际的插件行为。