Maven 仓库
在 Maven 坐标与依赖 中详细介绍了 Maven 坐标和依赖,坐标和依赖是任何一个构件在 Maven 世界中的逻辑表示方式;而构件的物理表示方式是文件, Maven 通过仓库来统一管理这些文件。本章将详细介绍 Maven 仓库,在了解了 Maven 如何使用仓库之后,将能够更高效地使用 Maven。
1. 何为 Maven 仓库
在 Maven 世界中,任何一个依赖、插件或者项目构建的输出,都可以称为构件。例如,依赖 log4j-1.2.15.jar 是一个构件,插件 maven-compiler-plugin-2.0.2.jar 是一个构件。任何一个构件都有一组坐标唯一标识。
得益于坐标机制,任何 Maven 项目使用任何一个构件的方式都是完全相同的。在此基础上, Maven 可以在某个位置统一存储所有 Maven 项目共享的构件,这个统一的位置就是仓库。实际的 Maven 项目将不再各自存储其依赖文件,它们只需要声明这些依赖的坐标,在需要的时候(例如,编译项目的时候需要将依赖加入到 classpath 中) Maven 会自动根据坐标找到仓库中的构件,并使用它们。
2. 仓库的布局
任何一个构件都有其唯一的坐标,根据这个坐标可以定义其在仓摩中的唯一存储路径,这便是 Maven 的仓库布局方式。例如,log4j:log4j:1.2.15 这一依赖,其对应的仓库路径为 log4j/logs4j/1.2.15/log4j-1.2.15.jar,细心的读者可以观察到,该路径与坐标的大致对应关系为 groupId/artifactId/version/artifactId-version.packaging。
考虑这样一个构件: groupId=org.testng、 artifactId=testng、 version=5.8、 classifier=jilk15、 packaging=jar,其对应的路径按如下步骤生成:
(1) 基于构件的 groupId 准备路径, 将 groupId 中的句点分隔符转换成路径分隔符。该例中, groupId=org.testng 就会被转换成 org/testng,之后再加一个路径分隔符斜杠,那么,org.testng 就成为了org/ tesing/
(2) 基于构件的 artifactId 准备路径,也就是在前面的基础上加上 artifactId 以及一个路径分隔符。该例中的 artifactId 为 testng,那么,在这一步过后,路径就成为了 org/testng/testng/
(3) 使用版本信息。在前面的基础上加上 version 和路径分隔符。该例中版本是 5.8,那么路径就成为了 org/testng/tesing/5.8/
(4) 依次加上 artifactId,构件分隔符连字号,以及 version,于是构建的路径就变成了 org/testng/testng/5.8/testng-5.8。
(5) 如果构件有 classifier,就加上构件分隔符和 classifier。该例中构件的 classifier 是 jdkl5,那么路径就变成 org/testng/testng/5.8/testng-5.8-jdk5。
(6) 检查构件的 extension,若 extension 存在,则加上句点分隔符和 extension。packaging 决定了构件的扩展名,该例的 packaging 是 jar,因此最终的路径为 org/testng/testng/5.8/testng-5.8-jdk5.jar
Maven 仓库是基于简单文件系统存储的,我们也理解了其存储方式,因此,当遇到些与仓库相关的问题时,可以很方便地查找相关文件,方便定位问题。例如,当 Maven 无法获得项目声明的依赖时,可以查看该依赖对应的文件在仓库中是否存在,如果不存在,查看是否有其他版本可用,等等。
3. 仓库的分类
对于 Maven 来说,仓库只分为两类:本地仓库和远程仓库。当 Maven 根据坐标寻找构件的时候,它首先会查看本地仓库,如果本地仓库存在此构件,则直接使用;如果本地仓库不存在此构件,或者需要查看是否有更新的构件版本, Maven 就会去远程仓库査找,发现需要的构件之后,下载到本地仓库再使用。如果本地仓库和远程仓库都没有需要的构件,Maven 就会报错。
在这个最基本分类的基础上,还有必要介绍一些特殊的远程仓库。中央仓库是 Maven 核心自带的远程仓库,它包含了绝大部分开源的构件。在默认配置下,当本地仓库没有 Maven 需要的构件的时候,它就会尝试从中央仓库下载。
私服是另一种特殊的远程仓库,为了节省带宽和时间,应该在局域网内架设一个私有的仓库服务器,用其代理所有外部的远程仓库。内部的项目还能部署到私服上供其他项日使用。
3.1 本地仓库
编辑文件 ~/.m/settings.xml,设置 localRepository 元素的值为想要的仓库地址。例如:
<settings>
<localRepository>D:/maven/repository</localRepository>
</settings>
3.2 远程仓库
由于最原始的本地仓库是空的, Maven 必须知道至少一个可用的远程仓库,才能在执行 Maven 命令的时候下载到需要的构件。中央仓库就是这样一个默认的远程仓库, Maven 的安装文件自带了中央仓库的配置。读者可以使用解压工具打开 jar 文件 MAVEN_HOME/lib/maven-model-builder-3.5.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。最后需要注意的是 snapshots 元素,其子元素 enabled 值为 false,表示不从中央仓库下载快照版本的构件。
3.3 私服
私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,私服代理广域网上的远程仓库,供局域域网内的 Maven 用户使用。当 Maven 需要下载构件的时候,它从私服请求,如果私服上不存在该构件,则从外部的远程仓库下载,缓存在私服上之后,再为 Maven 的下载请求提供服务。此外,一些无法法从外部仓库下载到的构件也能从本地上传到私服上供大家使用。
4. 远程仓库的配置
在很多情况下,默认的中央仓库无法满足项目的需求,可能项目需要的构件存在于另外一个远程仓库中,如 Jboss Maven 仓库。这时,可以在 POM 中配置该仓库:
<project>
<repositories>
<repository>
<id>jboss</id>
<name>JBoss Repository</name>
<url>https://repository.maven.com/maven2</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
在 repositories 元素下,可以使用 repository 子元素声明一个或者多个远程仓库。该例中声明了一个 id 为 jboss,名称为 Jboss Repository 的仓库。任何一个仓库声明的 id 必须是唯一的,尤其需要注意的是, Maven 自带的中央仓库使用的 id 为 central,如果其他的仓库声明也使用该 id,就会覆盖中央仓库的配置。该配置中的 url 值指向了仓库的地址,一般来说,该地址都基于 http 协议, Maven 用户都可以在测览器中打开仓库地址浏览构件。
该例配置中的 releases 和 snapshots 元素比较重要,它们用来控制 Maven 对于发布版构件和快照版构件的下载。这里需要注意的是 enabled 子元素,该例中 releases 的 enabled 值为 true,表示开启 JBOSS 仓库的发布版本下载支持,而 snapshots 的 enabled 值为 false,表示关闭 Jboss 1仓库的快照版本的下载支持。因此,根据该配置, Maven 只会从 JBOSS 仓库下载发布版的构件,而不会下载快照版的构件。
该例中的 layout 元素值 default 表示仓库的布局是 Maven2 及 Maven3 的默认布局,而不是 Maven1 的布局。
对于 releases 和 snapshots 来说,除了 enabled,它们还包含另外两个子元素 updatePolicy 和 checksumPolicy:
<snapshots>
<enabled>false</enabled>
<updatePolicy>daily</updatePolicy>
<checksumPolicy>ignore</checksumPolicy>
</snapshots>
元素 updatePolicy 用来配置 Maven 从远程仓库检查更新的频率,默认的值是 daily,表示 Maven 每天检査一次。其他可用的值包括: never一从不检查更新; always-一每次构建都检查更新; interval:X一每每隔X分钟检查一次更新(X为任意整数)。
元素 checksumPolicy 用来配置 Maven 检查检验和文件的策略。当构件被部署到 Maven 仓库中时,会同时部署对应的校验和文件。在下载构件的时侯, Maven 会验证校验和文件,如果校验和验证失败,怎么办?当 checksumPolicy 的值为默认的 warn 时, Maven 会在执行构建时输出警告信息,其他可用的值包括:fail- Maven 遇到校验和错误就让构建失败;ignore一使 Maven 完全忽略校验和错误。
4.1 远程仓库的认证
大部分远程仓库无须认证就可以访问,但有时候出于安全方面的考虑,我们需要提供认证信息才能访问一些远程仓库。例如,组织内部有一个 Maven 服务器,该服务器为每个项目都提供独立的 Maven 仓库,为了防止非法的仓库访问,管理员为每个仓库提供了一组用户名及密码。这时为了能让 Maven 访问仓库内容,就需要配置认证信息。
配置认证信息和配置仓库信息不同,仓库信息可以直接配置在 POM 文件中,但是认证信息必须配置在 settings.xml 文件中。这是因为 POM 往往是被提交到代码仓库中供所有成员访问的,而 settings.xml 一般只放在本机。因此,在 settings.xml 中配置认证信息更为安全。假设需要为一个 id 为 jboss 的仓库配置认证信息,编辑 settings.xml 文件见代码清单a
<settings>
<servers>
<server>
<id>jboss</id>
<username>repo-user</username>
<password>repo-pwd</password>
</server>
</servers>
</settings>
Maven 使用 settings.xml 文件中并不显而易见的 servers元 素及其 server 子元素配置仓库认证信息。jboss 仓库的认证用户名为 repo-user,认证密码为 repo-pwd。这里的关键是id 元素, settings.xml 中 server 元素的 id 必须与 POM 中需要认证的 repository 元素的 id 完全一致。换句话说,正是这个 id 将认证信息与仓库配置联系在了一起。
4.2 部署至远程仓库
私服的一大作用是部署第三方构件,包括组织内部生成的构件以及一些无法从外部仓库直接获取的构件。无论是日常开发中生成的构件,还是正式版本发布的构件,都需要部署到仓库中,供其他团队成员使用。
Maven 除了能对项目进行编译、测试、打包之外,还能将项目生成的构建部署到仓库中。首先,需要编辑项目的 pom.xml 文件。配置 distributionManagement 元素:
<project>
<distributionManagement>
<repository>
<id>proj-releases</id>
<name>Proj Release Repository</name>
<url>http://127.0.0.1/content/repositories/proj-releases</url>
</repository>
<snapshotRepository>
<id>proj-snapshots</id>
<name>Proj Snapshots Repository</name>
<url>http://127.0.0.1/content/repositories/proj-snapshots</url>
</snapshotRepository>
</distributionManagement>
</project>
distributionManagement 包含 repository 和 snapshotRepository 子元素,前者表示发布版本构件的仓库,后者表示快照版本的仓库。这两个元素下都需要配置 id、name 和 url,id 为该远程仓库的唯一标识,name 是为了方便人阅读,关键的 url 表示该仓库的地址。
往远程仓库部署构件的时候,往往需要认证。简而言之,就是需要在 settings.Xml 中创建一个 server 元素,其 id 与仓库的 id 匹配,并配置正确的认证信息。不论从远程仓库下载构件,还是部署构件至远程仓库,当需要认证的时候,配置的方式是一样的。配置正确后,在命令行运行 mvn clean deploy, Maven 就会将项目构建输出的构件部署到配置对应的远程仓库,如果项目当前的版本是快照版本,则部署到快照版本仓库地址,否则就部署到发布版本仓库地址。
5. 快照版本
在 Maven 的世界中,任何一个项目或者构件都必须有自己的版本。版本的值可能是 1.0.0、1.3-alpha-4、2.0、2.1-SNAPSHOT 或者 2.1-20091214.221414-13。其中,1.0.01.3- alpha-4 和 2.0 是稳定的发布版本,而 2.1-SNAPSHOT 和 2.1-20091214.221414-13 是不稳定的快照版本。
快照版本只应该在组织内部的项目或模块间依赖使用,因为这时,组织对于这些快照版本的依赖具有完全的理解及控制权。项目不应该依赖于任何组织外部的快照版本依赖,由于快照版本的不稳定性就是说,即使项目构建今天是成功的,由于外部的快照版依实际对应的构件随时可能变化,项目的构建就可能由于这些外部的不受控制的失败。
6. 从仓库解析依赖的机制
第5章详细介绍了 Maven 的依赖机制,本章又深入阐述了 Maven 仓库,这两者是如何具体联系到一起的呢? Maven 是根据怎样的规则从仓库解析并使用依赖构件的呢?当本地仓库没有依赖构件的时侯, Maven 会自动从远程仓库下载;当依赖版本为快照版本的时侯, Maven 会自动找到最新的快照。这背后的依赖解析机制可以概括如下:
(1) 当依赖的范围是 system 同的时候, Maven 直接从本地文件系统解析构件。
(2) 根据依赖坐标计算仓库路径后,尝试直接从本地仓库寻找构件,如果发现相应构件,则解析成功。
(3) 在本地仓库不存在相应构件的情况下,如果依赖的版本是显式的发布版本构件,如 1.2、2.1-beta-1 等,则遍历所有的远程仓库,发现后,下载并解析使用。
(4) 如果依赖的版本是 RELEASE 或者 LATEST,则基于更新策略读取所有远程仓库的元数据 groupId/artifactId/maven-metadata.xml,将其与本地仓库的对应元数据合并后,计算出 RELEAS 或者 LATEST 真实的值,然后基于这个真实的值检查本地和远程仓库,如步骤 (2)和 (3)。
(5) 如果依赖的版本是 SNAPSHOT,则基于更新策略读取所有远程仓库的元数据 groupId/artifactId/version/maven-metadata.xml,将其与本地仓库的对应元数据合并后,得到最新快照版本的值,然后基于该值检查本地仓库,或者从远程仓库下载。
(6) 如果最后解析得到的构件版本是时间戳格式的快照,如 1.4.1-20091104.121450-121,则复制其时间戳格式的文件至非时间戳格式,如 SNAPSHOT,并使用该非时间戳格式的构件。
当依赖的版本不明晰的时候,如 RELEASE、 LATEST 和 SNAPSHOT, Maven 就需要基于更新远程仓库的更新策略来检査更新。在第 4 节提到的仓库配置中,有一些配置与此有关:首先是
的配置。
当 Maven 检查完更新策略,并决定检查依赖更新的时候,就需要检查仓库元数据 maven-metadata.xml。
回顾一下前面提到的 RELEASE 和 LATEST 版本,它们分别对应了仓库中存在的该构件的最新发布版本和最新版本(包含快照),而这两个“最最新”是基于 groupId/artifactId/maven-metadata.xml 计算出来的:
<?xml version="1.0" encoding="UTF-8" ?>
<metadata>
<groupId>org.sonatype.nexus</groupId>
<artifactId>nexus</artifactId>
<versioning>
<latest>1.4.2-SNAPSHOT</latest>
<release>1.4.0</latest>
<versions>
<version>1.3.5</version>
<version>1.3.6</version>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.0</version>
<version>1.4.0.1-SNAPSHOT</version>
<version>1.4.1-SNAPSHOT</version>
<version>1.4.2-SNAPSHOT</version>
</versions>
</versioning>
</metadata>
该 XML 文件列出了仓库中存在的该构件所有可用的版本,同时 latest 元素指向了这些版本中最新的那个版本,该例中是 1.4.2-SNAPSHOT。而 release 元素指向了这些版本中最新的发布版本,该例中是 1.4.0。 Maven 通过合并多个远程仓库及本地仓库的元数据,就能计算出基于所有仓库的 latest 和 release 分别是什么,然后再解析具体的构件。
需要注意的是,在依赖声明中使用 LATEST 和 RELEASE 是不推荐的做法,因为 Maven 随时都可能解析到不同的构件,可能今天 LATEST 是 1.3.6,明天就成为 1.4.O-SNAPSHOT 了,且 Maven 不会明确告诉用户这样的变化。当这种变化造成构建失败的时候,发现问题会变得比较困难。 RELEASE 因为对应的是最新发布版构建,还相对可靠, LATEST 就非常不可靠了,为此,Maven3 不再支持在插件配置中使用 LATEST 和 RELEASE。如果不设置插件版本,其效果就和 RELEASE 一样, Maven 只会解析最新的发布版本构件。不过过即使这样,也还存在潜在的问题。例如,某个依赖的 1.1 版本与 1.2 版本可能发生一些接口的变化,从而导致当前 Maven 构建的失败。
当依赖的版本设为快照版本的时候, Maven 也需要检查更新,这时,Maven 会检查仓库元数据 groupId/artifactId/maven-metadata.xml,见代码清单6-64
<?xml version="1.0" encoding="UTF-8" ?>
<metadata>
<groupId>org.sonatype.nexus</groupId>
<artifactId>nexus</artifactId>
<version>1.4.2-SNAPSHOT</version>
<versioning>
<snapshot>
<timestamp>20091214.221414</timestamp>
<buildNumber>13</buildNumber>
</snapshot>
<lastUpdated>20191214221558</lastUpdated>
</versioning>
</metadata>
该 XML 文件的 snapshot 元素包含于 timestamp 和 buildNumber 两个子元素,分别代表这一快照的时间戳和构建号,基于这两元素可以得到该仓库中此快照的最新构件版本实际为 1.4.2-20091214.221414-13。通过合并所有远程仓库和本地仓库的元数据, Maven 就能知道所有仓库中该构件的最新快照。
最后,仓库元数据据并不是水远正确的,有时候当用户发现无法解析某些构件,或者解析得到错误构件的时候,就有可能是出现了仓库元数据错误,这时就需要手工地,或者使用工具(如 Nexus)对其进行修复。
7. 镜像
如果仓库 X 可以提供仓库 Y 存储的所有内容,那么就可以认为 X 是 Y 的一个镜像。换句话说,任何一个可以从仓库 Y 获得的构件,都能够从它的镜像中获取。举个例子,http://maven.net.cn/content/groups/public/ 是中央仓库 http:/repo.maven.org/maven2/ 在中国的镜像,由于地理位置的因素,该镜像往往能够提供比中央仓库更快的服务。因此,可以配置 Maven 使用该镜像来替代中央仓库。编辑 settings.xml:
<settings>
<mirrors>
<mirror>
<id>maven.net.cn</id>
<name>one of the central mirrors in China</name>
<url>http://maven.net.cn/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
</settings>
该例中,
关于镜像的一个更为常见的用法是结合私服。由于私服可以代理任何外部的公共仓库(包括中央仓库),因此,对于组织内部的 Maven 用户来说,使用用一个私服地址就等于使用了所有需要的外部仓库,这可以将配置集中到私服,从而简化 Maven 本身的配置。在这种情况下,任何需要的构件都可以从私服获得,私服就是所有仓库的镜像。这时,可以配置这样的一个镜像:
<settings>
<mirrors>
<mirror>
<id>internal-repository</id>
<name>Internal Repository Manager</name>
<url>http:/192.168.1.100/maven2/</url>
<mirrorOf>*</mirrorOf>
</mirror>
</mirrors>
</settings>
该例中
为了满足一些复杂的需求, Maven 还支持更高级级的镜像配置:
<mirrorof>*</mirrorof>
匹配所有远程仓库<mirrors>external:*</mirrorof>
匹配所有远程仓库,使用 localhost 的除外,使用 file://协议的除外。也就是说,匹配所有不在本机上的远程仓库。<mirrors>repo,repo2</mirrored>
匹配仓库 repo 和 repo2,使用逗号分隔多个远程仓库。<mirrors>*, ! repo1</mirrors>
匹配所有远程仓库, repo1 除外,使用感叹号将仓库从匹配中排除。
需要注意的是,由于镜像仓库完全屏蔽了被镜像仓库,当镜像仓库不稳定或者停止服务的时候, Maven 仍将无法访问被镜像仓库,因而将无法下载构件。
8. 仓库搜索服务
(1) Sonatype Nexus
地址: http://repository.sonatype.org/
Nexus 是当前最流行的开源 Maven 仓库管理软件,本书后面会有专门的章节讲述如何使用 Nexus 假设私服。