徐晓斌的《Maven实战》,实在是一本很不错的书,本文也是在我读过这本书的仓库一章后,根据自己的经验,做的一个总结。

仓库

先介绍一个构件和仓库的概念。

构件:在Maven中,任何一个依赖、插件或者项目构建的输出,都是一个构件。如:依赖包是一个构件、编译的插件是一个构件;

仓库:Maven在某个位置统一存储所有Maven项目共享的构件,这个统一的位置就是仓库。

仓库的分类

对于Maven来说,仓库只有两种:本地仓库和远程仓库。但是还有一类特殊的远程仓库——私服,它是在局域网内架设的私有仓库服务器。

Repertory

本地仓库

配置

默认情况下,不管在window还是在Linux系统上,每一个用户在自己用户目录下都有一个.m2/repository/的仓库目录。

当然,Maven是允许我们自定义本地仓库目录地址的,在Maven的安装目录的conf下找到settings.xml文件,配置如下:

1
2
3
<settings>
<localRepository>/home/matt/maven/repo</localRepository>
</settings>

这样本地仓库就被设置在/home/matt/maven/repo下。

关于settings.xml文件,有一点需要注意的是:

  • $M2_HOME/conf/settings.xml:是全局范围的,整台机器上的所有用户都会直接受到该配置的影响;
  • ~/.m2/settings.xml:是用户范围的,只有当前用户才会受到该配置的影响(若目录下没有此文件,可将上个settings.xml复制一份到本目录下再去修改)。

本地项目安装到本地仓库

在这个本地项目下,执行

1
mvn clwan install

Install插件的install目标就是将项目的构建输出文件安装到本地仓库。

中央仓库

最原始的本地仓库是空的,Maven的安装文件中自带了中央仓库的配置。在Maven的安装目录下的/lib/maven-model-builder-XX.jar的jar包中的pom-4.0.0.xml中有如下的配置:

1
2
3
4
5
6
7
8
9
10
11
<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,所以都会有这个配置。该中央仓库包含了世界上绝大多数流行的开源Java构件,以及源码、作者信息、SCM、信息、许可证等。上面的snapshots元素的子元素enabled设置为false,表示不会从该中央仓库下载快照版本的构件。

私服

私服是架设在局域网内的仓库服务,私服代理广域网上的远程仓库,供局域网用户使用。当Maven需要下载构件的时候,他从私服请求,如果私服不存在当前构件,则从外部的远程仓库缓存到私服上之后再为Maven的下载请求提供服务,此外一些无法从外部仓库下载到的构件也可以手工上传到私服上供大家使用。

它主要有以下几个优点:

  • 节省外网带宽;
  • 加速Maven构建;
  • 部署第三方构件(尤其是组织内部的构件);
  • 提高稳定性,增强控制;
  • 降低中央仓库的负荷。

远程仓库的配置

配置远程仓库

很多情况下,默认的中央仓库无法满足项目的需求,可能项目需要的构件存在于另外一个远程仓库,可以在POM进行配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
<repositories>
<repository>
<id>jboss</id>
<name>JBoss Repository</name>
<url>http://repository.jboss.com/maven2/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>

关于配置远程仓库,主要有以下几点需要注意:

  • 任何仓库的id必须唯一;
  • repositories元素下,可以使用repository子元素声明一个或者多个远程仓库;
  • 元素releasessnapshots用来控制Maven对于发布版本构件和快照版构件的下载。

对于元素releasessnapshots,除了enabled子元素外,它们还包括另外两个子元素updatePolicychecksumPolicy

1
2
3
4
5
<snapshots>
<enabled>false</enabled>
<checksumPolicy>ignore</checksumPolicy>
<updatePolicy>daily</updatePolicy>
</snapshots>
  • updatePolicy:配置Maven从远程仓库检查更新的频率,默认值是dailynever:从不检查、always:每次构建时都检查、interval:X:每隔X分钟检查一次);
  • checksumPolicy:配置Maven检验和文件的策略,默认值为warn:在执行构建时输出警告信息(ignore:完全忽略校验和错误,fail:遇到校验和错误就让构建失败)。

认证

有些远程仓库基于安全的考虑需要提供认证信息才可以访问。配置认证信息需要在settings.xml文件中配置:

1
2
3
4
5
<server>
<id>deploymentRepo</id>
<username>repouser</username>
<password>repopwd</password>
</server>

Maven使用settings.xml文件中并不显而易见的server子元素配置仓库认证信息。

配置认证信息和配置仓库信息不同,仓库信息可以这配置在POM文件中,但是认证信息必须配置在settings.xml文件中。、

部署到远程仓库

Maven除了对项目进行编译,测试和打包之外,还能将项目部署到仓库中,首先编辑POM文件添加distributionManagementy元素。

1
2
3
4
5
6
7
8
9
10
11
<distributionManagement>
<repository>
<id>proj-releases</id>
<name>Proj Releases Repository</name>
<url>http://192.168.1.100/content/repositories/proj-releases</url>
</repository>
<snapshotRepository>
<id>proj-snapshots</id>
<name>Proj Snapshots Repository</name>
<url>http://192.168.1.100/content/repositories/proj-snapshots</url>
</snapshotRepository>

distributionManagement包含repositorysnapshotRepository子元素,前者表示发布版本构件的仓库,后者表示快照版本的仓库。这两个元素下都需要配置idnameurlid为该仓库的唯一标识,name是为了方便人阅读,关键的url表示该仓库的地址。

配置正确后,执行:

1
mvn clean deploy

Maven就会将构建输出的构件部署到配置对应的远程仓库,如果项目当前的版本是快照版本,则部署到快照版本仓库地址,否则就部署到发布版本仓库地址。

镜像

如果仓库X可以提供仓库Y存储的所有内容,那么就可以认为X是Y的镜像。配置如下:

1
2
3
4
5
6
7
8
<mirrors>
<mirror>
<id>mirrorId</id>
<mirrorOf>repositoryId</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://my.repository.com/repo/path</url>
</mirror>
</mirrors>

<mirrorOf> central</mirrorOf>表示只为central仓库做镜像,如果想为所有的仓库做镜像那么可以改为:

1
<mirrorOf>*</mirrorOf>

mirrorOf可选择的配置主要有以下几个:

  • *:配置所有的仓库;
  • external*:匹配所有的远程仓库,使用localhost的除外,使用file://的除外;
  • repo1,repo2:匹配仓库repo1和repo2,使用逗号分隔多个仓库;
  • *,!repo1:匹配所有的远程仓库除了repo1.

仓库的内部机制

这一部分主要介绍一下Maven仓库的三个内部机制,一个仓库的布局方式,两个是仓库的解析机制和更新策略。

仓库的布局方式

任何一个构件都有其唯一的坐标,根据这个坐标可以定义其在仓库中的唯一存储路径,这便是Maven仓库布局方式。例如,log4j:log4j:1.2.15这一依赖,其对应的仓库路径为log4j/log4j/1.2.15/log4j-1.2.15.jar。该路径与坐标的大致对应关系为groupId/artifactId/version/artifactId-version.packaging

具体的可以参看Maven参考布局的源码部分,也可以参考《Maven实战》P76-77。

仓库解析依赖机制

Maven是根据怎么的跪着从仓库解析并使用依赖构件的呢?

当本地仓库没有依赖构件的时候,Maven会自动从远程仓库下载相应构件,当依赖版本为快照版本的时候,Maven会自动找到最新的快照。详细的依赖机制为:

  1. 当依赖的范围是system的时候,Maven直接从本地文件系统解析构件;
  2. 根据依赖坐标计算仓库路径后,尝试直接从本地仓库寻找构件,如果发现相应构件,则解析成功;
  3. 在本地仓库不存在相应构件的情况下,如果依赖的版本是显式的发布版本构件,如:1.2,2.1等,则遍历所有的远程仓库,发现后,下载并解析使用;
  4. 如果依赖的版本是RELEASE或者LATEST,则基于更新策略读取所有远程仓库的元数据groupId/artifactId/mavenmetadata.xml,将其与本地仓库的对应元数据合并后,计算出RELEASE或者LATEST真实的值,然后基于这个真实的值检查本地和远程仓库,如步骤1和3;
  5. 如果依赖的版本是SNAPSHOT,则基于更新策略读取所有远程仓库的元数据groupId/artifactId/version/mavenmetadata.xml,将其与本地仓库的对应元数据合并后,得到最新快照版本的值,然后基于该值检查本地仓库,或者从远程仓库下载;
  6. .如果最后解析得到的构件版本是时间戳格式的快照,如:1.4-20091104.121450-121,则复制其时间戳格式的文件到非时间戳格式,如:SNAPSHOT,并使用该非时间戳格式的构件.

当依赖的版本不明晰的时候,如:RELEASE,LATESTSNAPSHOT,Maven就需要基于更新远程仓库的更新策略来检查更新。

仓库的更新策略

主要有以下几个配置与Maven仓库的更新有关:

  • 首先是<releases><enabled><snapshots><enabled>,只有仓库开启了对于发布版本的支持时,才能访问该仓库的发布版本构件信息,对于快照版本也是同理;
  • 其次要注意的是
    <releases><snapshots>的子元素<updatePolicy>,该元素配置了检查更新的频率;
  • 最后,用户还可以从命令行加入参数-U,强制检查更新,使用参数后,Maven就会忽略<updatePolicy>的配置。

当Maven检查完更新策略,并决定检查依赖更新的时候,就需要检查仓库元数据maven-metadata.xml。前面提到的RELEASELATEST版本,它们分别对应了仓库中存在的该构件的最新发布版本和最新版本(包含快照),而这两个”最新”是基于groupId/artifactId/maven-metadata.xml计算出来的,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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</release>
<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元素指向了这些版本中最新的那个版本。而release元素指向了这些版本中最新的发布版本。Maven通过合并多个远程仓库及本地仓库的元数据,就能计算出基于所有仓库的latestrelease分别是什么,然后再解析具体的构件。

有几点需要的注意的:

  • 在依赖声明中使用LATEST和RELEASE是不推荐的做法(因为Maven随时都可能解析到不同的构件,可能今天LATEST1.3.6,明天就成了1.4.0-SNAPSHOT了,且Maven不会明确告诉用户这样的变化);
  • Maven3不再支持在插件配置中使用LATESTRELEASE
  • 如果不设置插件版本,其效果就和RELEASE一样,Maven只会解析最新的发布版本构件;

当依赖的版本设为快照版本的时候,Maven也需要检查更新,这时,Maven会检查仓库元数据groupId/artifactId/version/maven-metadata.xml,这个与发布版本的有所不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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>20091214221558</lastUpdated>
</versioning>
</metadata>

该xml文件的snapshot元素包含timestampbuildNumber两个子元素,分别代表了这一快照的时间戳和构建号,基于这两个元素可以得到该仓库中此快照的最新构件版本实际为1.4.2-20091213.221414-13。通过合并所有远程仓库和本地仓库的元数据,Maven就能知道所有仓库中该构件的最新快照。

快照版本

在Maven的世界中,任何一个项目或者构件都必须有自己的版本。版本的值可能是1.0.0,1.3-alpha-4,2.0,2.1-SNAPSHOT或者2.1-20091214.221414-13。其中,1.01.3-alpha-42.0是稳定的发布版本,而2.1-SNAPSHOT2.1-20091214.221414-13是不稳定的快照版本。

快照版本对于Maven来说是很重要的,下面举个例子来说明。小张在开发模块A的2.1版本,该版本还未正式发布,与模块A一同开发的还有模块B,它由小张的同事季MM开发,B的功能依赖于A。在开发的过程中,小张需要经常将自己最新的构建输出,交给季MM,供她开发和集成调试,问题是,这个工作如何进行呢?如果不停更新版本2.1.1、2.1.2、2.1.3….呢?首先,小张和季MM两人都需要频繁地更改POM,如果有更多的模块依赖于模块A,就会涉及更多的POM更改;其次,大量的版本其实仅仅包含了微小的差异,这样也会造成为版本号的滥用。

Maven的快照版本机制就是为了解决上述问题。在该例中,小张只需要将模块A的版本设定为2.1-SNAPSHOT,然后发布到私服中,在发布的过程中,Maven会自动为构件打上时间戳。比如:2.1-20091214.221414-13就表示2009年12月14日 22点14分14秒的第13次快照。有了该时间戳,Maven就能随时找到仓库中该构件2.1-SNAPSHOT版本最新的文件。这时,季MM配置对于模块A的2.1-SNAPSHOT版本的依赖,当她构件模块B的时候,Maven会自动从仓库中检查模块A的2.1-SNAPSHOT的最新构件,当发现有更新时便进行下载。默认情况下,Maven每天检查一次更新(由仓库配置的updatePolicy控制),用户也可以使用命令行-U参数强制让Maven检查更新,如:mvn clean install-U

基于快照版本机制,小张在构建成功之后才能将构件部署至仓库,而季MM可以完全不用考虑模块A的构建,并且她能确保随时得到模块A的最新可用的快照构件,而这一切都不需要额外的手工操作。

最后要注意的是,快照版本一般只在组织内部的项目或模块间依赖使用,而且项目不应该依赖于组织外部的快照版本依赖(因为快照版本是不稳定的)。


参考: