共计 6959 个字符,预计需要花费 18 分钟才能阅读完成。
这又是一个系列,一个要把 Maven 讲透的系列,希望能够对大家有帮助!
前言
在前面的几篇关于 Maven 的总结中,都说到只要指定了 groupId
、artifactId
和version
坐标信息,就可以从中央仓库中找到对应的 jar 包。等一下,仓库?大家肯定会问,仓库是什么?这个仓库在哪里?为什么 Maven 会自动去那个仓库找我要的 jar 包呢?好的,我知道大家肯定有一堆的疑问,一头雾水,这篇文章就来解决大家的这些疑问,拨开疑雾,对这个“仓库”一探究竟。跟着我的步伐,Let’s go!
Maven 仓库是什么?
现在大家想一下之间开发的非 Maven 项目,是不是在每个项目下面都有一个 lib 目录。是的,你不用去翻看你以前做的项目了,没有错,没有 Maven 之前,我们项目依赖的包,我们都会下载下来,统一放到对应项目的 lib 目录下去。同一个包,比如 Spring 框架的包,项目 A 要使用,就拷贝一份到项目 A 的 lib 目录下去;项目 B 也要使用,那就再拷贝一份到项目 B 的目录下去。这样下去,你会发现同样的依赖包,需要拷贝 N 份,这样不仅造成了磁盘空间的浪费,而且也难于统一管理。
现在好了,有了 Maven,基于 Maven 的坐标机制,任何 Maven 项目使用任何一个构件的方式都是完全相同的。在此基础上,Maven 可以在某个位置统一存储所有 Maven 项目共享的包,而这个统一存放依赖包的位置就是仓库。说白了,Maven 仓库就是存放依赖包的地方。
有了这个 Maven 仓库后,上面的问题就有了一个完美的解决方案。基于 Maven 开发的项目不再各自存储其依赖文件,它们只需要声明这些依赖的坐标,在需要的时候,Maven 会自动根据坐标找到仓库中的包,并正确使用它们。
仓库的分类
在使用 Maven 的过程中,我们需要知道 Maven 去哪里找那个所谓的“仓库”,从而加载依赖。所以,我们就需要知道 Maven 仓库的分类。
在 Maven 中,仓库分为以下两类:
- 本地仓库
- 远程仓库
Maven 根据依赖坐标去仓库中找对应的包,是遵循这样的一个轨迹:
- 首先去本地仓库查找,如果本地仓库有对应的依赖包,则直接就使用;
- 如果本地仓库不存在对应包时,或者需要查看是否有更新的包版本时,Maven 就会去远程仓库查找,发现需要的构件之后,下载到本地仓库在使用;
- 如果本地仓库和远程仓库都没有需要的包,Maven 就会报错。
上面简单将 Maven 仓库进行的分类,但是对于远程仓库,它又分为好几种:
-
本地仓库
上面也说到了 Maven 根据依赖坐标去仓库中找对应的包是有遵循的轨迹的。Maven 最开始都是从本地仓库寻找依赖的包。默认情况下,不管是在 Windows 还是在 Linux 上,每个用户在自己的用户目录下都有一个路径名为.m2/repository
的仓库目录。这个就是默认的本地仓库地址。有的时候,用户可能会自定义本地仓库的目录地址(我一般都会这么干)。此时,可以通过编辑
~/.m2/settings.xml
,设置localRepository
元素的值就 OK 了,比如我的是这样子的:<localRepository>E:/repository</localRepository>
这样,该用户的本地仓库地址就被设置成了 E:/repository。
-
中央仓库
由于最开始的本地仓库是空的,Maven 必须知道至少一个可用的远程仓库,这样才能在执行 Maven 命令的时候下载到需要的构件。中央仓库就是这样一个默认的远程仓库,Maven 的安装文件中自带了中央仓库的配置。使用解压缩工具打开$M2_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>
pom-4.0.0.xml 文件是所有 Maven 项目都会继承的超级 POM,这段配置使用 id central 对中央仓库进行唯一标识,同时设置 snapshots 元素,其子元素 enabled 的值为 false,表示不从该中央仓库下载快照版本的包。
中央仓库是一个大而全的包仓库,它包含了这个世界上绝大多数流行的开源 Java 包,以及源码等信息。一般来说,一个简单 Maven 项目所需要的依赖包都能从中央仓库下载到,这也就解释了为什么 Maven 能做到“开箱即用”。
-
私服
玩游戏的时候,经常会听到私服。但是在学习 Maven 的时候,也听到私服,这个就比较特殊了。在 Maven 中,私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,私服代理广域网上的远程仓库,供局域网内的 Maven 用户使用。整体架构如下图所示:当 Maven 需要下载依赖包的时候,它从私服请求,如果私服上不存在该依赖包,则从外部的远程仓库下载,缓存到私服上之后,再为 Maven 的下载请求提供服务。另外,一些无法从外部仓库下载到的依赖包也能从本地上传到私服上供大家使用。
为啥要用私服呢?肯定是有少好处的。像在我们公司,在全国 31 个省都有分公司,同时总部研发中心还会开发一堆的公共 JAR 包,给 31 个分公司使用,这样通过私服就可以很好的解决研发中心和分公司之间的公共包分发等问题。对于使用私服,它有以下这些优点:
- 加快 Maven 构建;我们知道,不停的连接外部仓库下载依赖包是一件非常耗费时间的事情,而私服部署在局域网,则可以大大的降低依赖包的下载时间,提高 Maven 构建效率;
- 部署第三方包;比如我们公司的研发中心,会开发很多公共的包,而这些包又无法上传至中央仓库,所以这些包部署在私服就再适合不过了。
远程仓库配置
没有一个平台能够大而全到包含所有的东西,同理,中央仓库也是这样的,虽然它包含了我们需要的大部分的依赖包,但是还是有一些包在中央仓库中是找不到的。这个时候,我们就需要配置一些其它远程仓库来补充中央仓库中没有的依赖包,与中央仓库配合完成工作,当中央仓库也没有对应的依赖包时,Maven 则遍历所有的远程仓库。
我们需要在 pom.xml 中配置即可,比如这样:
<project>
......
<!-- 配置远程仓库 -->
<repositories>
<repository>
<id>jboss</id>
<name>JBoss Repository</name>
<url>http://repository.jboss.com/maven2/</url>
<releases>
<enabled>true</enabled>
<updatePolicy>daily</updatePolicy>
</releases>
<snapshots>
<enabled>false</enabled>
<checksumPolicy>warn</checksumPolicy>
</snapshots>
<layout>default</layout>
</repository>
</repositories>
......
</project>
-
repository
:在repositories
元素下,可以使用repository
子元素声明一个或者多个远程仓库。 -
id
:仓库声明的唯一 id,尤其需要注意的是,Maven 自带的中央仓库使用的 id 为 central,如果其他仓库声明也使用该 id,就会覆盖中央仓库的配置。 -
name
:仓库的名称,让我们直观方便的知道仓库是哪个,暂时没发现其他太大的含义。 -
url
:指向了仓库的地址,一般来说,该地址都基于 http 协议,Maven 用户都可以在浏览器中打开仓库地址浏览构件。 -
releases
和snapshots
:用来控制 Maven 对于发布版构件和快照版构件的下载权限。需要注意的是 enabled 子元素,该例中 releases 的 enabled 值为 true,表示开启 JBoss 仓库的发布版本下载支持,而 snapshots 的 enabled 值为 false,表示关闭 JBoss 仓库的快照版本的下载支持。根据该配置,Maven 只会从 JBoss 仓库下载发布版的构件,而不会下载快照版的构件。 -
layout
:元素值 default 表示仓库的布局是 Maven2 及 Maven3 的默认布局,而不是 Maven1 的布局。基本不会用到 Maven1 的布局。 - 其他:对于 releases 和 snapshots 来说,除了 enabled,它们还包含另外两个子元素 updatePolicy 和 checksumPolicy。
元素 updatePolicy 用来配置 Maven 从远处仓库检查更新的频率,默认值是 daily,表示 Maven 每天检查一次。其他可用的值包括:never- 从不检查更新;always- 每次构建都检查更新;interval:X- 每隔 X 分钟检查一次更新(X 为任意整数)。
元素 checksumPolicy 用来配置 Maven 检查校验和文件的策略。当构建被部署到 Maven 仓库中时,会同时部署对应的检验和文件。在下载构件的时候,Maven 会验证校验和文件,如果校验和验证失败,当 checksumPolicy 的值为默认的 warn 时,Maven 会在执行构建时输出警告信息,其他可用的值包括:fail-Maven 遇到校验和错误就让构建失败;ignore- 使 Maven 完全忽略校验和错误。
大部分公共的远程仓库无须认证就可以直接访问,但我们在平时的开发中往往会架设自己的 Maven 远程仓库,出于安全方面的考虑,我们需要提供认证信息才能访问这样的远程仓库。配置认证信息和配置远程仓库不同,远程仓库可以直接在 pom.xml 中配置,但是认证信息必须配置在 settings.xml 文件中。这是因为 pom 往往是被提交到代码仓库中供所有成员访问的,而 settings.xml 一般只存在于本机。因此,在 settings.xml 中配置认证信息更为安全。比如这样配置:
<servers>
<server>
<id>deploymentRepo</id>
<username>repouser</username>
<password>repopwd</password>
</server>
</servers>
这里的关键是 id 元素,settings.xml 中 server 元素的 id 必须与 pom.xml 中需要认证的 repository 元素的 id 完全一致。正是这个 id 将认证信息与仓库配置联系在了一起。
部署至远程仓库
很多时候,我们编译完成后,会将我们的负责的模块包部署至私服,以供其它团队成员使用。那如何将我们的包部署到远程仓库呢?
我们配置项目的 pom.xml 文件即可,配置如下所示:
<project>
......
<distributionManagement>
<repository>
<id>releases</id>
<name>public</name>
<url>http://59.50.95.66:8081/nexus/content/repositories/releases</url>
</repository>
<snapshotRepository>
<id>snapshots</id>
<name>Snapshots</name>
<url>http://59.50.95.66:8081/nexus/content/repositories/snapshots</url>
</snapshotRepository>
</distributionManagement>
......
</project>
distributionManagement 包含 repository 和 snapshotRepository 子元素,前者表示发布版本(稳定版本)构件的仓库,后者表示快照版本(开发测试版本)的仓库。
配置正确后,运行 mvn clean deploy
命令,Maven 就会将项目构建输出的构件部署到配置对应的远程仓库,如果项目当前的版本是快照版本,则部署到快照版本的仓库地址,否则就部署到发布版本的仓库地址。
快照版本
如果去你的本地仓库,你可能会看到以这种类似于 1.0.0、1.3-alpha-1、2.0、2.1-SNAPSHOT 或者 2.1-20190412.221213-52 这样的版本号命名的 JAR 包。其中,1.0.0、1.3-alpha- 1 和 2.0 是稳定的发布版本,而 2.1-SNAPSHOT 和 2.1-20190412.221213-52 是不稳定的快照版本。
Maven 为什么要区分发布版本和快照版本呢?为什么还要有 2.1-SNAPSHOT 和 2.1-20190412.221213-52 这样的命名区分呢?
现在我们来思考一种现实的开发场景。现在软件开发都是多人分模块开发,比如小明开发 A 模块,你开发 B 模块,而你的 B 模块是需要依赖 A 模块的,在开发过程中,小明需要经常将自己最新的代码提交编译,生成 A 模块包,交给你,供你开发和集成调试。这种开发场景,如何完美的实现多人协同开发、模块开发呢?
你可能想你自己每次单独从代码更把 A 模块代码 Pull 下来,自己编译,生成自己需要的依赖包。这么做没有问题,但是带来的问题是你需要去拉代码,还要去编译别人的模块,如果编译不顺利,各种错误,你可能一脸懵逼,只能去找小明解决了。这样无形的就把工作流程搞砸了,你本来就只想要一个 A 模块的包,但是你却干了一堆不相干的事情,浪费时间。
你可能又想,小明每次将 A 模块编译完成后,使用 mvn clean deploy
命令部署到私服,这样你在编译你的模块时,Maven 就会自动的去私服下载对应的依赖包。想起来挺美的,但是在 Maven 中,同样的版本和同样的坐标就意味着同样的包,所以,如果你的本地仓库中已经包含了模块 A 的某个版本的包,Maven 就不会再对照远程仓库进行更新。除非你每次执行 Maven 命令之前,清除本地仓库,但这种要求手工干预的做法显然也是不可取的。
此时你可能又想,小明不断的修改 A 模块的版本号,你也按照小明提供的版本号,修改 A 模块的依赖版本。是的,这样是可以的,这就需要你和小明频繁的修改 POM,如果有更多的模块依赖,这个小问题就会变成一个大问题,而且还很容易出错。
Maven 的快照机制就是为了解决上述问题。在这个开发场景中,小明只需要将模块 A 的版本设定为 2.1-SNAPSHOT
,然后发布到私服中,在发布的过程中,Maven 会自动为包打上时间戳。比如上面的2.1-20190412.221213-52
就表示 2019 年 4 月 12 日 22 点 12 分 13 秒的第 52 次快照。有了这个时间戳,Maven 就能随时找到仓库中该包 2.1-SNAPSHOT
版本最新的文件。当你编译 B 模块时,Maven 会自动从仓库中检查模块 A 模块的 2.1-SNAPSHOT
的最新输出包,当发现有更新时便进行下载。
基于快照版本机制,在不需要额外手工操作的情况下,就能完美的解决上述问题。在知道了快照的原理之后,我们的项目不应该依赖任何快照版本的依赖包,由于快照版本的不稳定性,这样的依赖会造成潜在的危险。
镜像
如果仓库 X 可以提供仓库 Y 存储的所有内容,那么就可以认为 X 是 Y 的一个镜像。也就是说,任何一个可以从仓库 Y 获得的依赖包,都能够从它的镜像中获取。比如 http://maven.aliyun.com/nexus/content/groups/public
是阿里提供的中央仓库的镜像,由于地理位置等其它因素,该镜像往往能够提供比中央仓库更快的服务。因此,一般情况下,我们会配置镜像来替代中央仓库。编辑 settings.xml 文件即可,比如配置阿里提供的镜像:
<mirrors>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
mirrorOf
的值为 central,表示该配置为中央仓库的镜像,任何对于中央仓库的请求都会转至该镜像;id
、name
和 url
的配置与一般仓库配置一样,表示该镜像仓库的唯一标识符、名称以及地址。
总结
回头一看,Maven 仓库的知识点这么多。这篇文章主要以理解性的知识点为主,很多内容都参考了网上的其它博文。这里的知识点你不一定在使用 Maven 的过程中都会用到,但是理解了,就让你以后的工作更加得心应手。不求都会,但求上面的内容你都看一遍,心中对 Maven 仓库的概念和相关知识点有一个比较全面的理解和认识。全文读完,用不了十分钟。用十分钟学习一个知识点,还是非常值得的。
果冻想,玩代码,玩技术!
2019 年 4 月 13 日,于内蒙古呼和浩特。