Maven学习笔记
1. 是什么
基于Java的跨平台项目管理及自动构建工具
2. 入门
|
|
以上是Maven项目的基本配置文件,其余的各项配置写在<project></project>
标签内
3. 坐标和依赖
3.1 入门
我们先看一组坐标定义
- groupId:定义当前Maven项目隶属的实际目录,groupId不应该对应项目隶属的额组织或者公司,由于组织下会有许多实际项目,groupId只定义到组织级别,artifactId只能定义Maven项目(模块),实际项目将难以定义。
- artifactId:定义实际项目中的一个Maven项目(模块),推荐使用实际项目名称作为artifactId的前缀
- version:定义Maven项目所处的版本
- packaging:定义Maven项目的打包方式,打包方式通常与所生成构件的文件扩展名对应,打包方式会影响到构建的生命周期,不定义packaging的时候,Maven会使用默认至jar。
- classifier:定义构建输出的一些附属构建,附属构建与主构建对应。如上例中主构建是nexus-indexer-2.0.0jar,该项目可能还会通过使用一些插件生成如nexus-indexer-2.0.0-javadoc.jar、nexus-indexer-2.0.0-sources.jar这样一些附属构建,其中包含了Java文档和源代码。javadoc和sources就是这两个附属构建的classifier。不能直接定义项目的classifier,因为附属构件不是项目直接默认生成的,而是有附件的插件帮助生产
上述5个元素中,groupId、artifactId、version是必须定义的,packaging是可选的(默认为jar),而classifier是不能直接定义的。
项目构件的文件名是与坐标相对应的额,一般的规则为artifactId-version[-classifier].packaging。
3.2 详解
一个依赖可以包含如下的一些元素:
- type:依赖的类型,对应于项目坐标定义的packaging。大部分情况下,该元素不必声明,默认为jar。
- scope:依赖范围,见3.2.1
- optional:依赖是否可选
- exclusions:排除依赖传递性
3.2.1 scope详解
scope有以下几种以来范围:
- compile:编译依赖范围。如果没有指定默认使用该依赖范围。此种依赖范围对于编译、测试、运行三种classpath都有效,如spring-core。
- test:测试依赖范围。对测试classpath有效,在编译主代码或者运行项目时无法使用此依赖,如Junit。
- provided:已提供依赖范围。对编译和测试classpath有效,但在运行时无效。如servlet-api,编译和测试时需要,运行时由容器提供,不需要重复引用。
- runtime:运行时依赖范围。对测试和运行有效,编译时无效。如JDBC驱动实现,项目代码只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。
system:系统依赖范围。同provided,对编译和测试classpath有效,但在运行时无效,但是必须通过systempath元素显示指定依赖文件的路径。不可移植,可引用环境变量。如:
1234567<dependency><groupId>javax.sql</groupId><artifactId>jdbc-stdext</artifactId><version>2.0</version><scope>system</scope><systemPath>${java.hone}/lib/rt.jar</systemPath></dependency>import:导入依赖范围,只在dependencyManagement元素下有效,见6.2.2
3.3 传递性依赖
3.3.1 什么是传递性依赖
Mavan会解析各个直接依赖的POM,将必要的间接以来,以传递性以来的形式引入到当前的项目中。如项目依赖spring-core,sprng-core又依赖commons-logging,那commons-logging就会成为当前项目的依赖范围。
3.3.2 传递性依赖和依赖范围
依赖范围不仅控制依赖与三种classpath的关系,还会对传递性依赖产生影响。最左边一列表示直接依赖范围,第一行表示第二直接依赖范围。
compile | test | provided | runtime | |
---|---|---|---|---|
compile | compile | - | - | runtime |
test | test | - | - | test |
provided | provided | - | provided | provided |
runtime | runtime | - | - | runtime |
3.4 依赖调解
当项目的依赖关系中对同一依赖有不同版本的描述,就会发生依赖调节,遵循一下两条原则
- 路径最近者有限:如A->X(1.0)->C,A->B->c->X(2.0),X(1.0)长度为2,X(2.0)长度为4,Maven会选择最近的X(1.0)
- 第一声明者优先:当依赖路径长度相等的前提下,在POM中依赖声明中顺序最考前的依赖会被解析使用
3.5 可选依赖
|
|
该项目中将Project-A声明为可选依赖,如果其他项目依赖该项目并要使用Project-A,就需要显示的声明Project-A的依赖。理想情况下我们不应该使用可选依赖,而是分别创建一个Maven项目。
3.6 实战
3.6.1 排除依赖
当项目中的某个类库不稳定会影响到当前项目或者要替换某个传递性依赖时就需要排除依赖。具体是在<dependency></dependency>
中增加<ecxlusions></ecxlusions>
标签。
我们将Project-B排除,注意不需要version,因为groupId和artifactId能唯一确定依赖图中的某个依赖,在以后的Maven解析中,不可能出现groupId和artifactId相同,但是version不同的两个依赖。
3.6.2 归类依赖
通过使用Maven属性在<version></version>
标签中使用${springframework.version}
来归类依赖
3.6.3 优化依赖
Maven会自定解析项目的直接依赖和传递性以来,依据股则判断依赖范围,对依赖冲突进行调节确保任何一个构件只有唯一的版本在依赖中存在,最后得到的那些依赖被称为已解析依赖(Resolved Dependency)。
mvn dependency:list
:查看当前项目的已解析依赖mvn dependency:tree
:查看当前项目的依赖树mvn dependency:analyze
:分析当前项目的依赖
4. 仓库
4.1 介绍
Maven在某个位置统一存储所有Maven项目共享的构建,实际的Maven项目将不再各自存储其依赖的文件。
4.2 仓库的布局
Maven构件的存储路径为:groupId/artifactId/version/artifactId-version[-classifier].packaging。句点分割会被转化成路径分割,如log4j:log4j:1.2.15对应的仓库路径为log4j/log4j/1.2.15/log4j-1.2.15.jar。
4.3 仓库分类
仓库分为两类:本地仓库和远程仓库,优先查找本地仓库是否有构件。远程仓库有两类特殊仓库:中央仓库和私服。
- 中央仓库:Maven核心自带的远程仓库,包含了绝大部分开源构建。默认本地没有Maven需要的构建时会从中央仓库下载。
- 私服:在局域网内架设的私有仓库服务器,用来代理所有外部的远程仓库,内部项目可以部署到私服上供其他项目使用。
4.3.1 本地仓库
默认情况下,有一个路径名为~/.m2/repository/的仓库,如果用户想要自定义仓库的位置,可以编辑该文件。如:
默认情况下该文件不存在,用户需要从$M2_HOME/conf/settings.xml复制再编辑,不推荐直接修改全局目录的settings.xml文件,因为每次升级该文件都会被覆盖。
4.3.2 中央仓库
由于必须有至少一个可用的远程仓库才能在执行Maven命令时下载到需要的构件,可以解压Maven的jar文件,然后访问路径org/apache/maven/model/pom-4.0.0.xml查看pom中央仓库配置。所有的Maven都会继承该超级POM。
4.3.3 私服
一种特殊的远程仓库,架设在局域网内。当Maven需要下载构件的时候,先向私服请求,如果私服上不存在,则会从外部的远程仓库下载,缓存在私服上。无法从外部仓库下载到的构件也能从本地上传到私服供大家使用。
节省外网带宽 | 消除对外重复构件的下载 |
---|---|
加速Maven构建 | Maven的一些内部机制(如快照更新检查)会在执行构建的时候不停地检查远程仓库数据,当配置了很多外部仓库的时候,构建的速度会被大大降低。使用私服时只需要检查局域网内私服的数据 |
部署第三方构件 | 私有构件无法从外部仓库获得,部署到内部仓库,供内部的Maven项目使用 |
提高稳定性,增强控制 | 防止Internet不稳定的时候,Maven构建变得不稳定。一些私服软件(如Nexus)还提供了许多额外的功能,如权限管理、RELEASE/SNAPSHOT区分等,管理员可以对仓库进行一些更高级的控制 |
降低中央仓库的负荷 | 避免对中央仓库重复的下载 |
4.4 远程仓库的配置
|
|
4.4.1 远程仓库的认证
大部分远程仓库无需认证就可以访问,但有些时候出于安全方面的考虑,需要提供认证信息才能访问一些远程仓库,如私服等。
认证信息必须配置在settings.xml文件中,POM往往被提交到代码仓库中供所有成员访问,为了保证安全写在本机的settings.xml文件
4.4.2 部署至远程仓库
私服的一个作用是部署第三方构件,Maven除了能对项目进行编译、测试、打包之外,还能将项目生成的构建部署到仓库中。
1.首先编辑pom文件
123456789101112131415<project><!-- 写在distributionManagement元素中 --><distributionManagement><repository> <!-- 指定发布版本构件的仓库 --><id>...</id><name>...</name> <!-- name是方便人阅读 --><url>...</url></repository><snapshotRepository> <!-- 指定快照版本构件的仓库 --><id>...</id><name>...</name><url>...</url></snapshotRepository></distyibutionManagement></project>2.往远程仓库部署构件时往往需要认真,要配置认证信息
- 3.执行
mvn clean deploy
,部署到远程仓库
4.5 快照版本
Maven中任何一个项目或者构件都必须有自己的版本,本分分为发布版和快照版。
发布版:稳定,版本值可能是1.0.0、1.3alpha-4、2.0
快照版:不稳定,版本值2.1-SNAPSHOT、2.1-20091214.221414-13
只需将版本号后加-SNAPSHOT,然后发布到私服中,Maven就会自动为构件打上时间戳,如2.1-20091214.221414-13 表示2009年12月14日22时14分14秒第13次快照。
默认情况下,Maven每天检查一次更新(由仓库配置的updatePolicy控制),用户也可以强制让Maven检查更新,如mvn clean install -U
4.6 从仓库解析依赖的机制
当本地仓库没有依赖构件时,Maven会自动从远程仓库下载;当依赖版本为快照版本时,Maven会自动找到最新的快照。其依赖解析机制如下
- 1)依赖范围是system,Maven直接从本地文件系统解析构件。
- 2)根据依赖坐标计算仓库路径,尝试直接直接从本地仓库寻找构件,如果发现相应构件,则解析成功。
- 3)本地仓库不存在相应构件的情况下,如果依赖的版本是显式的发布版本构件(如1.0,2.1-beta-1),遍历所有远程仓库,发现后下载并解析使用
- 4)如果依赖版本是RELEASE和LATEST,则基于更新策略读取所有远程仓库的元数据groupId/artifactId/maven-metadata.xml,将其与本地仓库的对应元数据合并后,计算出RELEASE或LATEST的真实的值,然后基于这个值检查本地和远程仓库,如步骤2)和3)。
- 5)如果依赖版本是SNAPSHOT,则基于更新策略读取所有远程仓库的元数据groupId/artifactId/version/maven-metadata.xml,将其与本地仓库的对应元数据合并后,得到最新快照版本的值,然后基于该值检查本地仓库和远程仓库。
- 6)如果最后解析得到的构件版本是时间戳格式的快照,如1.4.1-20091104.121450-121,则复制其时间戳格式文件至非时间戳格式,如SNAPSHOT,并使用该非时间戳格式的构件。
当依赖的版本不明晰的时候,如RELEASE、LATEST和SNAPSHOT,Maven需要基于更新远程仓库的更新策略来检查更新。与<release>
和<snapshots>
中的子元素<enable>
和<updatePolicy>
有关,只有enable为true才能访问该仓库对应发布版本的构件信息。依赖声明中不推荐使用LATEST和RELEASE,因为Maven随时可能解析到不同的构件,且Maven不会明确告诉用户这样的变化。
Maven3不再支持在插件配置中使用LATEST和RELEASE,如果不设置插件版本,其效果和RELEASE一样,Maven只会解析最新的发布版本构件
4.7 镜像
如果仓库X可以提供仓库Y存储的所有内容,那么就可以认为X是Y的一个镜像。使用镜像的目的是提供比中央仓库更快的服务,修改settings.xml如下。
<mirrorOf>
标签的匹配规则如下:
- *:匹配所有远程仓库
- external:*:匹配所有远程仓库,使用localhost和file://协议的除外。也就是说,匹配所有不在本机上的远程仓库
- repo1,repo2:匹配仓库repo1和repo2,逗号分隔
- ,!repo1:匹配除repo1以外的所有远程仓库,感叹号排除
*镜像完全屏蔽了仓库镜像,当镜像仓库不稳定或者停止服务的时候,Maven仍将无法访问被镜像的仓库,因而无法下载构件
4.8 仓库搜索服务
5. 生命周期和插件
5.1 生命周期简介
Maven从大量项目和构建工具中学习和反思,总结了一套高度完善的、易扩展的生命周期。包含项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等。
Maven的生命周期是抽象的,实际的任务都交由插件来完成。
5.2 生命周期详解
5.2.1 三套生命周期
Maven拥有三套相互独立的生命周期,分别为clean、default和site,调用这三者中的一个不会触发其它两个生命周期。
- clean:清理项目
- default:构建项目
- site:建立目标站点
每个生命周期包含一些有序阶段,并且后面的阶段依赖于前面的阶段。如clean生命周期包含pre-clean、clean和post-clean,调用pre-clean的时候,只有pre-clean阶段执行调用clean的时候pre-clean和clean阶段都会执行。
5.2.2 clean生命周期
clean生命周期的目的是清理项目,它包含三个阶段:
- pre-clean:执行一些清理前需要完成的工作
- clean:清理上一次构建生成的文件
- post-clean:执行一些清理后需要完成的工作
5.2.3 default生命周期
default是所有生命周期中的核心,定义了真正构建时所需要执行的所有步骤
- validate:验证这个项目是否正确,所有必需资源是否可用
- initialize:初始化编译的状态,例如:设置一些properties属性,或者创建一些目录
- generate-sources:生成所有在编译阶段需要的源代码
- process-sources:处理项目主资源文件。一般来说,是对src/main/resources目录的内容进行变量替换等工作后,复制到项目输出的主classpath目录中。
- generate-resources:生成这个项目包所有需要包含的资源文件
- process-resources:复制并处理资源文件到目标目录,为packaging 打包阶段做好准备
- compile:编译项目的主源码,一般来说,是编译src/main/java目录下的Java文件至项目输出的主classpath目录中
- process-classes:后置处理编译阶段生成的文件,例如:做java字节码的加强操作
- generate-test-sources:生成编译阶段需要的test源代码
- process-test-sources:处理项目测试资源文件。一般来说,是对src/main/resources目录的内容进行变量替换等工作后,复制到项目输出的测试classpath目录中。
- generate-test-resources:生成test测试需要的资源文件
- process-test-resources:复制并处理资源文件到test测试目标目录
- test-compile:编译项目的测试代码到指定test目标目录
- process-test-classes:后置处理test编译阶段生成的文件,例如:做java字节码的加强操作
- test:使用合适的单元测试框架,运行所有测试例子,这些测试用例不应该要求这些代码被打包或者部署才能执行
- prepare-package:处理任何需要在正式打包之前要完成的必须的准备工作。这一步的通常结果是解压,处理包版本等
- package:打包编译后的代码成可发包格式,例如:jar,war等
- pre-integration-test:完成一些在集成测试之前需要做的预处理操作,这通常包括建立需要的环境。
- integration-test:处理并部署(deploy)包到集成测试可以运行的环境中
- post-integration-test:处理一些集成测试之后的事情,通常包括一些环境的清理工作
- verify:做一些对包的验证操作,去检测这个包是一个合法的符合标准的包。
- install:将包安装到本地仓库,供本地其他Maven项目使用
- deploy:将最终的包复制到远程仓库,供其他开发人员和Maven项目使用
5.2.4 site生命周期
site生命周期的目的是建立和发布项目站点,Maven能够基于POM所包含的信息,自动生成一个友好的站点,方便团队交流和发布项目信息
- pre-site:执行一些在生成项目站点之前需要完成的工作
- site:生成项目站点文档
- post-site:执行一些在生成项目站点之后需要完成的工作
- site-deploy:将生成的项目站点发布到服务器上
5.2.5 命令行与生命周期
我们前面说过各个生命周期是相互独立的,但一个生命周期的阶段是有前后依赖关系的
mvn clean
:调用clean生命周期的clean阶段,实际执行clean生命周期的pre-clean和clean阶段mvn test
:调用default生命周期的test阶段,实际执行default生命周期的validate、initiaalize等直到test的所有阶段mvn clean install
:调用clean生命周期的clean阶段和default生命周期的install阶段,实际执行了clean生命周期的pre-clean、clean阶段以及default生命周期的从validate至install的所有阶段。结合了两个生命周期,非常适用于项目构建之前清理项目mvn clean deploy site-deploy
:实际执行clean生命周期的pre-clean 和clean ,default生命周期的所有阶段,以及site生命周期的所有阶段
5.3 插件目标与绑定
Maven核心仅仅定义了抽象的生命周期,实际的任务是交由插件完成,生命周期与插件相互绑定,用以完成实际的构建任务。一个插件有多个功能,每个功能就是一个插件目标。通用写法是插件前缀:插件目标。
5.3.1 自定义绑定
|
|
5.4 插件配置
5.4.1 命令行插件配置
Maven命令中使用-D参数,并伴随一个参数键=参数值的形式
例如:install–Dmaven.test.skip=true,跳过测试
5.4.2 POM中插件全局配置
我们可以在声明插件的时候,对此插件进行一个全局的配置让所有基于该插件的目标任务都使用这些配置。
5.4.3 POM中插件任务配置
除了配置插件的全局参数,我们还可以为某个插件任务配置特定的参数,如输出语句
6. 聚合与继承
6.1 聚合
当我们将项目化作多个模块的时候,我们需要使用聚合,聚合模块与其它模块的目录结构通常有水平或者父子两种关系。
//TODO:聚合图
先来看一下父子目录结构的POM
如果使用平行目录结构,聚合模块的POM需要做相应的修改
6.2 继承
前面说过所有POM实际都继承自超级POM,使用继承的好处就是减少部分重复配置。
要使用继承,首先要先定义父项目的POM
子模块POM修改为
Maven会首先根据relativePath检查父POM,如果找不到,再从本地仓库查找。relativePath的默认值为../pom.xml
在实际的应用中一个聚合POM可能同时又是父POM,这样管理比较方便
6.2.1 可继承的POM元素
- groupId :项目组 ID ,项目坐标的核心元素;
- version :项目版本,项目坐标的核心元素;
- description :项目的描述信息;
- organization :项目的组织信息;
- inceptionYear :项目的创始年份;
- url :项目的 url 地址
- develoers :项目的开发者信息;
- contributors :项目的贡献者信息;
- distributionManagerment :项目的部署信息;
- issueManagement :缺陷跟踪系统信息;
- ciManagement :项目的持续继承信息;
- scm :项目的版本控制信息;
- mailingListserv :项目的邮件列表信息;
- properties :自定义的 Maven 属性;
- dependencies :项目的依赖配置;
- dependencyManagement :醒目的依赖管理配置;
- repositories :项目的仓库配置;
- build :包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等;
- reporting :包括项目的报告输出目录配置、报告插件配置等。
6.2.2 依赖管理
由于dependencies也可以被继承,但有时子模块不会用到父模块的所有依赖,这时我们可以使用dependencyManagement元素来管理依赖配置。该元素的依赖声明不会引入实际的依赖,但是能够约束dependencies下的依赖使用
import:
如果想要导入合并某个POM中的dependencyManagement配置,我们需要用到import的范围依赖,如
6.2.3 插件管理
Maven也提供了pluginManagement元素帮助管理插件,该元素不会造成实际的插件调用行为,只是当POM中配置了真正的plugin元素,并且其groupId和artifactId与pluginManagement中配置的插件匹配时才会影响行为。
7. 使用Maven进行测试
7.1 跳过测试
7.1.1 仅跳过单元测试
命令:mvn package -DskipTests
POM文件:
7.1.2 跳过单元测试编译和测试
命令:mvn package -Dmaven.test.skip=true
POM文件:
8. 灵活构建
8.1 Maven属性
Maven有6类属性:
- 内置属性:主要两个内置属性——\${basedir}表示项目根目录,即包含pom.xml文件的目录;\${version}标识项目版本。
- POM属性:可以使用该类属性引用POM文件中对应元素的值,例如${project.artifactId}对应了\
\ 元素的值
常见的POM属性包括:${project.build.sourceDirectory}:项目的主源码目录,默认为src/main/java/. ${project.build.testSourceDirectory}:项目的测试源码目录,默认为/src/test/java/. ${project.build.directory}:项目构建输出目录,默认为target/. ${project.build.outputDirectory}:项目主代码编译输出目录,默认为target/classes/. ${project.build.testOutputDirectory}:项目测试代码编译输出目录,默认为target/testclasses/. ${project.groupId}:项目的groupId. ${project.artifactId}:项目的artifactId. ${project.version}:项目的version,等同于${version} ${project.build.finalName}:项目打包输出文件的名称,默认为${project.artifactId}${project.version}.
自定义属性:在POM的\
元素下定义自定义Maven属性。如 1234<properties><!-- swagger.version --><swagger.version>2.2.2</swagger.version></properties>Settings属性:跟pom属性同理,使用setting开头,引用setting.xml文件中xml元素的值
- Java系统属性:所有的java系统属性都可以使用Maven属性引用,如\&{user.home}指向了用户目录
- 环境变量属性:所有环境变量都可以使用以env.开头的Maven属性引用。如:\${env.JAVAHOME}指代了JAVA_HOME环境变量的值。可以使用
mvn help:system
来查看所有的环境变量。
8.2 构建环境的差异
在不同的环境中,项目的源码应该使用不同的方式进行构建。最常见的就是数据库配置。
8.3 资源过滤
为了应对环境的变化,我们需要将会发生变化的部分提取出来。一般会在src/main/resources目录下添加数据库的配置文件.properties文件。
我们使用profile在POM中包起来
但是这仅仅不够,Maven属性默认只有在POM中才会被解析,上面的properties属性仍然没有被填充,为了解决这个问题我们需要定制maven-resources-plugin开启资源过滤
为主资源目录开启过滤:
同样为测试资源开启过滤:
主资源目录和测试资源目录都可以超过一个
属性激活:mvn clean install -Pdev
-P参数表示激活的profile
8.4 Maven Profile
在不同环境下的构建很可能是不同的,典型的情况就是数据库的配置。除此之外,有些环境可能需要配置插件执行一些特殊的操作,或者使用特殊版本的依赖,或者需要一个特殊的构件名称。为了能让构建在各个环境下方便地移植,Maven引入了profile的概念。profile能够在构建的时候修改POM的一个子集,或者添加额外的配置元素。用户可以使用很多方式激活profile,以实现构建在不同环境下的移植。
8.4.1 激活profile
- 命令激活
用户可以使用mvn命令行参数-P加上profile的id来激活profile,多个id之间以逗号分隔。例如,下面的命令激活了dev-x和dev-y两个profile:mvn clean install -Pdev-x, dev-y
settings文件显式激活
如果用户希望某个profile默认一直处于激活状态,就可以配置settings.xml文件的activeProfiles元素,表示其配置的profile对于所有项目都处于激活状态,代码如下:123456<settings>...<activeProfiles><activeProfile>dev-x</activeProfile></activeProfiles></settings>系统属性激活
用户可以配置当某系统属性存在或者为特定值的时候自动激活profile,如test为x123456789101112131415161718192021222324252627282930<profiles><profile><activation><property><name>test</name><value>x</value></property></activation></profile></profiles>```不要忘了用户可以在命令行声明系统属性`mvn clean install -Dtest=x`4. 操作系统环境激活5. 文件存在与否激活6. 默认激活定义profile的时候指定其默认激活```xml<profiles><profile><id>dev</id><activation><activeByDefault>true</activeByDefault></activation>...</profile></profiles>
如果POM中有任何一个profile通过以上其他任意一种方式被激活,所有的默认激活配置都会失效
了解当前激活的profile:mvn help:active-profiles
列出当前所有的profile:mvn help:all-profiles
8.4.2 profile的种类
根据具体的需要,可以在以下位置声明profile:
- pom.xml:pom.xml中声明的profile只对当前项目有效。
- 用户settings.xml:用户目录下.m2/settings.xml中的profile对本机上该用户所有的Maven项目有效。
- 全局settings.xml:Maven安装目录下conf/settings.xml中的profile对本机上所有的Maven项目有效,不建议使用。
参考资料
- 《Maven实战》
- Maven官方指南