@lemonguge
2017-09-13T14:49:57.000000Z
字数 11074
阅读 449
Maven
在Maven世界中,任何一个依赖、插件或者项目构建的输出,都可以称为构件
任何一个构件都有一组坐标唯一标识,根据这个坐标可以定义其在仓库中的唯一存储路径,这便是 Maven 的仓库布局方式。得益于坐标机制,任何 Maven 项目使用任何一个构件的方式都是完全相同的。在此基础上,Maven 可以在某个位置统一存储所有 Maven 项目共享的构件,这个统一的位置就是仓库。实际的 Maven 项目将不再各自存储其依赖文件,它们只需要声明这些依赖的坐标,在需要的时候(例如,编译项目的时候需要将依赖加入到 classpath 中),Maven 会自动根据坐标找到仓库中的构件,并使用它们。为了实现重用,项目构建完毕后生成的构件也可以安装或者部署到仓库中,供其他项目使用。
构件在仓库中的位置,groupId通过 . 分隔符转换成路径分隔符,通过 artifactId 作为一个路径,在加上 version 作为一个路径。例如 groupId=org.springframework、artifactId=spring-context、version=4.2.2.RELEASE,那么该构件在仓库中的位置为:org/springframework/spring-context/4.2.2.RELEASE/构件。
Maven 仓库是基于简单文件系统存储的,当 Maven 无法获得项目声明的依赖时,可以查看该依赖对应的文件在仓库中是否存在,如果不存在,查看是否有其他版本可用,等等。
对于 Maven 来说,仓库只分为两类:本地仓库和远程仓库
当 Maven 根据坐标寻找构件的时候,它首先会查看本地仓库,如果本地仓库存在此构件,则直接使用;如果本地仓库不存在此构件,或者需要查看是否有更新的构件版本,Maven 就会去远程仓库查找,发现需要的构件之后,下载到本地仓库再使用。如果本地仓库和远程仓库都没有需要的构件,Maven 就会报错。
中央仓库是 Maven 核心自带的远程仓库,它包含了绝大部分开源的构件。在默认配置下,当本地仓库没有 Maven 需要的构件的时候,它就会尝试从中央仓库下载。
私服是另一种特殊的远程仓库,为了节省带宽和时间,应该在局域网内架设一个私有的仓库服务器,用其代理所有外部的远程仓库。内部的项目还能部署到私服上供其他项目使用。
除了中央仓库和私服,还有很多其他公开的远程仓库,常见的有 Java.net Maven 库(http://download.java.net/maven/2/)和 JBoss Maven 库(http://repository.jboss.com/maven2/)等。
一个构件只有在本地仓库中之后,才能由其他 Maven 项目使用
默认情况下,不管是在 Windows 还是 Linux 上,每个用户在自己的用户目录下都有一个路径名为 .m2/repository/ 的仓库目录。本机的用户名是 lemonguge,在 Windows 机器上的本地仓库地址为 C:\Users\lemonguge.m2\repository。
Maven 也可以自定义本地仓库目录地址,这时,可以编辑文件 ~/.m2/settings.xml,设置 localRepository 元素的值为想要的仓库地址。
<settings><localRepository>E:/mvnRepository</localRepository></settings>
通过上面的配置,该用户的本地仓库地址就被设置成了 E:/mvnRepository。默认情况下,~/.m2/settings.xml 文件是不存在的,用户需要从 Maven 安装目录复制 $MAVEN_HOME/conf/settings.xml 文件再进行编辑。
安装好 Maven 后,如果不执行任何 Maven 命令,本地仓库目录是不存在的。当用户输入第一条 Maven 命令之后,Maven 才会创建本地仓库,然后根据配置和需要,从远程仓库下载构件至本地仓库。
这好比藏书。例如,我想要读《红楼梦》,会先检查自己的书房是否已经收藏了这本书,如果发现没有这本书,于是就跑去书店买一本回来,放到书房里。可能有一天我又想读一本英文版的《程序员修炼之道》,而书房里只有中文版,于是又去书店找,可发现书店没有,好在还有网上书店,于是从 Amazon 买了一本,几天后我收到了这本书,又放到了自己的书房。
本地仓库就好比书房,我需要读书的时候先从书房找,相应地,Maven 需要构件的时候先从本地仓库找。远程仓库就好比书店(包括实体书店、网上书店等),当我无法从自己的书房找到需要的书的时候,就会从书店购买后放到书房里。当 Maven 无法从本地仓库找到需要的构件的时候,就会从远程仓库下载构件至本地仓库。一般地,对于每个人来说,书房只有一个,但外面的书店有很多,类似地,对于 Maven 来说,每个用户只有一个本地仓库,但可以配置访问很多远程仓库。
由于最原始的本地仓库是空的,Maven 必须知道至少一个可用的远程仓库,才能在执行 Maven 命令的时候下载到需要的构件。中央仓库就是这样一个默认的远程仓库,Maven 的安装文件自带了中央仓库的配置。读者可以使用解压工具打开 jar 文件 $M2_HOME/lib/maven-model-builder-3.0.jar,然后访问路径 org/apache/maven/model/pom-4.0.0.xml,可以看到:
<repositories><repository><id>central</id><name>Central Repository</name><url>https://repo.maven.apache.org/maven2</url><layout>default</layout><snapshots><enabled>false</enabled></snapshots></repository></repositories>
所有 Maven 项目都会继承的超级 POM,这段配置使用 id为 central 对中央仓库进行唯一标识,其名称为 Central Repository,它使用 default 仓库布局。对于 Maven 1 的仓库,需要配置 layout 的值为 legacy。最后需要注意的是 snapshots 元素,其子元素 enabled 的值为 false,表示不从该中央仓库下载快照版本的构件。
私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,私服代理广域网上的远程仓库,供局域网内的 Maven 用户使用。当 Maven 需要下载构件的时候,它从私服请求,如果私服上不存在该构件,则从外部的远程仓库下载,缓存在私服上之后,再为 Maven 的下载请求提供服务。此外,一些无法从外部仓库下载到的构件也能从本地上传到私服上供大家使用。可以使用最流行的 Maven 私服软件——Nexus来架设,这会带来很多好处:
在很多情况下,默认的中央仓库无法满足项目的需求,可能项目需要的构件存在于另外一个远程仓库中,如 JBoss Maven 仓库。这时,可以在 POM 中配置远程仓库:
<repositories><repository><id>jboss</id><name>JBoss Repository</name><url>https://repository.jboss.org/maven2/</url><layout>default</layout><releases><enabled>true</enabled><updatePolicy>never</updatePolicy></releases><snapshots><enabled>false</enabled></snapshots></repository></repositories>
需要注意的是,Maven 自带的中央仓库使用的 id 为 central,如果其他的仓库声明也使用该 id,就会覆盖中央仓库的配置。该配置中的 url 值指向了仓库的地址,一般来说,该地址都基于 http 协议,Maven 用户都可以在浏览器中打开仓库地址浏览构件。
配置中的 releases 和 snapshots 元素比较重要,它们用来控制 Maven 对于发布版构件和快照版构件的下载。需要注意的是 enabled 子元素,该例中 releases 的 enabled 值为 true,表示开启 JBoss 仓库的发布版本下载支持,而 snapshots 的 enabled 值为 false,表示关闭 JBoss 仓库的快照版本的下载支持。因此根据该配置,Maven 只会从 JBoss 仓库下载发布版的构件,而不会下载快照版的构件。
layout 元素值 default 表示仓库的布局是 Maven 2 及 Maven 3 的默认布局,而不是 Maven 1 的布局。
对于 releases 和 snapshots 来说,除了 enabled,它们还包含另外两个子元素 updatePolicy和 checksumPolicy:
<snapshots><enabled>false</enabled><updatePolicy>always</updatePolicy><checksumPolicy>ignore</checksumPolicy></snapshots>
元素 updatePolicy 用来配置 Maven 从远程仓库检查更新的频率,有以下可用值:
daily:默认值,表示Maven每天检查一次always:每次构建都检查更新never:从不检查更新interval:X:每隔 X 分钟检查一次更新(X 为任意整数)元素 checksumPolicy 用来配置 Maven 检查检验和文件的策略,当构件被部署到Maven仓库中时,会同时部署对应的校验和文件。在下载构件的时候,Maven会验证校验和文件,如果校验和验证失败,怎么办?
warn:默认值,Maven 会在执行构建时输出警告信息fail:Maven遇到校验和错误就让构建失败ignore:使Maven完全忽略校验和错误大部分远程仓库无须认证就可以访问,但有时候出于安全方面的考虑,我们需要提供认证信息才能访问一些远程仓库。
配置认证信息和配置仓库信息不同,仓库信息可以直接配置在 POM 文件中,但是认证信息必须配置在 settings.xml 文件中。这是因为 POM 往往是被提交到代码仓库中供所有成员访问的,而 settings.xml 一般只放在本机,因此在 settings.xml 中配置认证信息更为安全。
假设需要为 id 为 Company-Snapshots 和 Company-Releases 的快照版本仓库配置认证信息,编辑 settings.xml 文件:
<settings><servers><server><id>Company-Snapshots</id><username>repouser</username><password>repopwd</password><server><server><id>Company-Releases</id><username>repouser</username><password>repopwd</password><server></servers></settings>
可以看到,该仓库的认证用户名为 repouser,认证密码为 repopwd。这里的关键是 id 元素,settings.xml 中 server 元素的 id 必须与 POM 中需要认证的 repository 元素的 id 完全一致。换句话说,正是这个 id 将认证信息与仓库配置联系在了一起。
私服的一大作用是部署第三方构件,包括组织内部生成的构件以及一些无法从外部仓库直接获取的构件。无论是日常开发中生成的构件,还是正式版本发布的构件,都需要部署到仓库中,供其他团队成员使用。需要编辑项目的 pom.xml 文件,配置 distributionManagement 元素:
<project><distributionManagement><repository><id>Company-Releases</id><name>Company Release Repository</name><url>http://192.168.1.100/content/repositories/company-releases</url></repository><snapshotRepository><id>Company-Snapshots</id><name>Company Snapshot Repository</name><url>http://192.168.1.100/content/repositories/company-snapshots</url></snapshotRepository></distributionManagement></project>
distributionManagement 元素包含 repository 和 snapshotRepository 子元素,前者表示发布版本构件的仓库,后者表示快照版本构件的仓库。这两个元素下都需要配置 id、name 和 url,id 为该远程仓库的唯一标识,name 是为了方便人阅读,url 表示该仓库的地址。
往远程仓库部署构件的时候,往往需要认证。就像上文所述,需要在 settings.xml 中创建一个 server 元素,其 id 与仓库的 id 匹配,并配置正确的认证信息。不论从远程仓库下载构件,还是部署构件至远程仓库,当需要认证的时候,配置的方式是一样的。
Maven 为什么要区分发布版和快照版呢?试想一下这样的情况,小张在开发模块 A 的 2.1 版本,该版本还未正式发布,与模块 A 一同开发的还有模块 B,它由小张的同事小季 开发,B 的功能依赖于 A。在开发的过程中,小张需要经常将自己最新的构建输出,交给小季,供她开发和集成调试,问题是,这个工作如何进行呢?
Maven 的快照版本机制就是为了解决上述问题。在该例中,小张只需要将模块A的版本设定为 2.1-SNAPSHOT,然后发布到私服中,在发布的过程中,Maven 会自动为构件打上时间戳。比如 2.1-20091214.221414-13 就表示 2009 年 12 月 14 日 22 点 14 分 14 秒的第 13 次快照。有了该时间戳,Maven 就能随时找到仓库中该构件 2.1-SNAPSHOT 版本最新的文件。这时,小季配置对于模块 A 的 2.1-SNAPSHOT 版本的依赖,当她构建模块 B 的时候,Maven 会自动从仓库中检查模块 A 的 2.1-SNAPSHOT 的最新构件,当发现有更新时便进行下载。
默认情况下,Maven 每天检查一次更新(由仓库配置的 updatePolicy 控制),用户也可以使用命令行 -U 参数强制让 Maven 检查更新,如 mvn clean install -U。
基于快照版本机制,小张在构建成功之后才能将构件部署至仓库,而小季可以完全不用考虑模块 A 的构建,并且她能确保随时得到模块 A 的最新可用的快照构件,而这一切都不需要额外的手工操作。
当项目经过完善的测试后需要发布的时候,就应该将快照版本更改为发布版本。例如,将 2.1-SNAPSHOT 更改为 2.1,表示该版本已经稳定,且只对应了唯一的构件。相比之下,2.1-SNAPSHOT 往往对应了大量的带有不同时间戳的构件,这也决定了其不稳定性。
当本地仓库没有依赖构件的时候,Maven 会自动从远程仓库下载;当依赖版本为快照版本的时候,Maven 会自动找到最新的快照。这背后的依赖解析机制可以概括如下:
system 的时候,Maven 直接从本地文件系统解析构件。RELEASE 或者 LATEST,则基于更新策略读取所有远程仓库的元数据 groupId/artifactId/maven-metadata.xml,将其与本地仓库的对应元数据合并后,计算出 RELEASE 或者 LATEST 真实的值,然后基于这个真实的值检查本地和远程仓库,如步骤 2 和 3。SNAPSHOT,则基于更新策略读取所有远程仓库的元数据 groupId/artifactId/version/maven-metadata.xml,将其与本地仓库的对应元数据合并后,得到最新快照版本的值,然后基于该值检查本地仓库,或者从远程仓库下载。SNAPSHOT,并使用该非时间戳格式的构件。当依赖的版本不明晰的时候,如 RELEASE、LATEST 和 SNAPSHOT,Maven 就需要基于更新远程仓库的更新策略来检查更新。仓库的配置与此有关,首先是 <releases><enabled> 和 <snapshots><enabled>,只有仓库开启了对于发布版本的支持时,才能访问该仓库的发布版本构件信息,对于快照版本也是同理;其次要注意的是 <releases> 和 <snapshots> 的子元素 <updatePolicy>,该元素配置了检查更新的频率,每日检查更新、永远检查更新、从不检查更新、自定义时间间隔检查更新等。最后,用户还可以从命令行加入参数 -U,强制检查更新,使用参数后,Maven 就会忽略 <updatePolicy> 的配置。
当 Maven 检查完更新策略,并决定检查依赖更新的时候,就需要检查仓库元数据 maven-metadata.xml。
<metadata><groupId>org.redisson</groupId><artifactId>redisson</artifactId><versioning><latest>2.2.22</latest><release>2.2.22</release><versions><!-- more versions ignore --><version>2.2.17</version><version>2.2.18</version><version>2.2.19</version><version>2.2.20</version><version>2.2.21</version><version>2.2.22</version></versions><lastUpdated>20160726143455</lastUpdated></versioning></metadata>
RELEASE 和 LATEST 版本,它们分别对应了仓库中存在的该构件的最新发布版本和最新版本(包含快照),而这两个 “最新” 是基于 groupId/artifactId/maven-metadata.xml 计算出来的。该 XML 文件列出了仓库中存在的该构件所有可用的版本,同时 latest 元素指向了这些版本中最新的那个版本,该例中是 2.2.22。而 release 元素指向了这些版本中最新的发布版本,该例中是 2.2.22。
在依赖声明中使用
LATEST和RELEASE是不推荐的做法
RELEASE 对应的是最新发布版构建,还相对可靠,LATEST 就非常不可靠了。
Maven 3 不再支持在插件配置中使用
LATEST和RELEASE。如果不设置插件版本,其效果就和RELEASE一样,Maven 只会解析最新的发布版本构件
当依赖的版本设为快照版本的时候,Maven 也需要检查更新,这时,Maven 会检查仓库元数据 groupId/artifactId/version/maven-metadata.xml
<metadata modelVersion="1.1.0"><groupId>com.fengdai</groupId><artifactId>core-finance-api</artifactId><version>0.0.1.M1-SNAPSHOT</version><versioning><snapshot><timestamp>20160728.014453</timestamp><buildNumber>136</buildNumber></snapshot><lastUpdated>20160728014453</lastUpdated><snapshotVersions><snapshotVersion><extension>jar</extension><value>0.0.1.M1-20160728.014453-136</value><updated>20160728014453</updated></snapshotVersion><snapshotVersion><extension>pom</extension><value>0.0.1.M1-20160728.014453-136</value><updated>20160728014453</updated></snapshotVersion></snapshotVersions></versioning></metadata>
该 XML 文件的 snapshot 元素包含了 timestamp 和 buildNumber 两个子元素,分别代表了这一快照的时间戳和构建号,基于这两个元素可以得到该仓库中此快照的最新构件版本实际为0.0.1.M1-20160728.014453-136。通过合并所有远程仓库和本地仓库的元数据,Maven 就能知道所有仓库中该构件的最新快照。
仓库元数据并不是永远正确的,有时候当用户发现无法解析某些构件,或者解析得到错误构件的时候,就有可能是出现了仓库元数据错误,这时就需要手工地,或者使用工具(如 Nexus)对其进行修复。
如果仓库 X 可以提供仓库 Y 存储的所有内容,那么就可以认为 X 是 Y 的一个镜像。换句话说,任何一个可以从仓库 Y 获得的构件,都能够从它的镜像中获取。举个例子,http://uk.maven.org/maven2/ 是中央仓库 https://repo.maven.apache.org/maven2 在中国的镜像,由于地理位置的因素,该镜像往往能够提供比中央仓库更快的服务。可以配置 Maven 使用该镜像来替代中央仓库,编辑 settings.xml:
<settings><mirrors><mirror><id>UK mirror</id><mirrorOf>central</mirrorOf><name>one of the central mirrors in China</name><url>http://uk.maven.org/maven2/</url></mirror></mirrors><settings>
<mirrorOf> 的值为 central,表示该配置为中央仓库的镜像,任何对于中央仓库的请求都会转至该镜像,也可以使用同样的方法配置其他仓库的镜像。另外三个元素 id、name、url 与一般仓库配置无异,表示该镜像仓库的唯一标识符、名称以及地址。类似地,如果该镜像需要认证,也可以基于该 id 配置仓库认证。
由于镜像仓库完全屏蔽了被镜像仓库,当镜像仓库不稳定或者停止服务的时候,Maven 仍将无法访问被镜像仓库,因而将无法下载构件
关于镜像的一个更为常见的用法是结合私服。由于私服可以代理任何外部的公共仓库(包括中央仓库),因此,对于组织内部的 Maven 用户来说,使用一个私服地址就等于使用了所有需要的外部仓库,这可以将配置集中到私服,从而简化 Maven 本身的配置。在这种情况下,任何需要的构件都可以从私服获得,私服就是所有仓库的镜像。
<settings><mirrors><mirror><id>internal-repository</id><mirrorOf>*</mirrorOf><name>Internal Repository Manager</name><url>http://192.168.1.100/maven2/</url></mirror></mirrors><settings>
<mirrorOf> 的值为星号,表示该配置是所有 Maven 仓库的镜像,任何对于远程仓库的请求都会被转至 http://192.168.1.100/maven2/。如果该镜像仓库需要认证,则配置一个 id 为 internal-repository 的<server> 即可。
<mirrorOf>*</mirrorOf>:匹配所有远程仓库<mirrorOf>external:*</mirrorOf>:匹配所有远程仓库,使用 localhost 的除外,使用 file:// 协议的除外,匹配所有不在本机上的远程仓库<mirrorOf>*</mirrorOf>:匹配仓库 repo1 和 repo2,使用逗号分隔多个远程仓库<mirrorOf>*,!repo1</mirrorOf>:匹配所有远程仓库,repo1 除外,使用感叹号将仓库从匹配中排除