2
这又是一个系列,一个要把Maven讲透的系列,希望能够对大家有帮助!

前言

在前面的几篇关于Maven的总结中,都说到只要指定了groupIdartifactIdversion坐标信息,就可以从中央仓库中找到对应的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根据依赖坐标去仓库中找对应的包,是遵循这样的一个轨迹:

  1. 首先去本地仓库查找,如果本地仓库有对应的依赖包,则直接就使用;
  2. 如果本地仓库不存在对应包时,或者需要查看是否有更新的包版本时,Maven就会去远程仓库查找,发现需要的构件之后,下载到本地仓库在使用;
  3. 如果本地仓库和远程仓库都没有需要的包,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需要下载依赖包的时候,它从私服请求,如果私服上不存在该依赖包,则从外部的远程仓库下载,缓存到私服上之后,再为Maven的下载请求提供服务。另外,一些无法从外部仓库下载到的依赖包也能从本地上传到私服上供大家使用。

    为啥要用私服呢?肯定是有少好处的。像在我们公司,在全国31个省都有分公司,同时总部研发中心还会开发一堆的公共JAR包,给31个分公司使用,这样通过私服就可以很好的解决研发中心和分公司之间的公共包分发等问题。对于使用私服,它有以下这些优点:

    1. 加快Maven构建;我们知道,不停的连接外部仓库下载依赖包是一件非常耗费时间的事情,而私服部署在局域网,则可以大大的降低依赖包的下载时间,提高Maven构建效率;
    2. 部署第三方包;比如我们公司的研发中心,会开发很多公共的包,而这些包又无法上传至中央仓库,所以这些包部署在私服就再适合不过了。

远程仓库配置

没有一个平台能够大而全到包含所有的东西,同理,中央仓库也是这样的,虽然它包含了我们需要的大部分的依赖包,但是还是有一些包在中央仓库中是找不到的。这个时候,我们就需要配置一些其它远程仓库来补充中央仓库中没有的依赖包,与中央仓库配合完成工作,当中央仓库也没有对应的依赖包时,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用户都可以在浏览器中打开仓库地址浏览构件。
  • releasessnapshots:用来控制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,表示该配置为中央仓库的镜像,任何对于中央仓库的请求都会转至该镜像;idnameurl的配置与一般仓库配置一样,表示该镜像仓库的唯一标识符、名称以及地址。

总结

回头一看,Maven仓库的知识点这么多。这篇文章主要以理解性的知识点为主,很多内容都参考了网上的其它博文。这里的知识点你不一定在使用Maven的过程中都会用到,但是理解了,就让你以后的工作更加得心应手。不求都会,但求上面的内容你都看一遍,心中对Maven仓库的概念和相关知识点有一个比较全面的理解和认识。全文读完,用不了十分钟。用十分钟学习一个知识点,还是非常值得的。

果冻想,玩代码,玩技术!

2019年4月13日,于内蒙古呼和浩特。


微信扫码关注公众号


果冻想
430 声望32 粉丝