第六章 仓库

China☆狼群 提交于 2019-12-05 05:42:28

坐标和依赖是任何一个构件在Maven世界中的逻辑表示方式;而构件的物理表示方式就是文件,Maven通过仓库来统一管理这些文件。 ###6.1 何为Maven仓库###   在Maven世界中,任何一个依赖、插件或者项目构建的输出,都可以称为构件。例如,依赖log4j-1.2.15.jar是一个构件,插件maven-compiler-plugin-2.0.2.jar是一个构件。任何一个构件都有一组坐标唯一标识。
  得益于坐标机制,任何Maven项目使用任何一个构件的方式都是完全相同的。在此基础上,Maven可以在某个位置统一存储所有Maven项目共享的构件,这个统一的位置就是仓库。实际的Maven项目将不再各自存储其他依赖文件,它们只需要声明这些依赖的坐标,在需要的时候(例如,编译项目的时候需要将依赖加入到classpath中),Maven会自动根据坐标找到仓库中的构件,并使用它们。
  为了实现重用,项目构建完毕后生成的构件也可以安装或者部署到仓库中,供其他项目使用。 ###6.2 仓库的布局###   任何一个构件都有其唯一的坐标,根据这个坐标可以定义其在仓库中的唯一储存路径,这便是Maven的仓库布局方式。例如,log4j:log4j:1.2.15这个依赖,其对应的仓库路径为log4j/log4j/1.2.15/log4j-1.2.15.jar,该路径与坐标的大致对应关系为groupId/artifactId/version/artifactId-version(-classifier).packaging。   Maven仓库是基于简单文件系统存储的。 ###6.3 仓库的分类###   对于Maven来说,仓库只有两类:本地仓库和远程仓库。当Maven根据坐标寻找构件的时候,它首先会查看本地仓库,如果本地仓库存在此构件,则直接使用;如果本地仓库不存在此构件,或者需要查看是否有更新的构件版本,Maven就会去远程仓库查找,发现需要的构件之后,下载到本地仓库再使用。如果本地仓库和远程仓库都没有需要的构件,Maven就会报错。
  有几类特殊的远程仓库。中央仓库是Maven核心自带的远程仓库,它包含了绝大部分开源的构件。在默认配置下,当本地仓库没有Maven需要的构件的时候,它就会尝试从中央仓库下载。私服是另一种特殊的远程仓库,为了节省带宽和时间,应该在局域网内架设一个私有的仓库服务器,用其代理所有外部的远程仓库。内部的项目还能部署到私服上供其他项目使用。除了中央仓库和私服,还有很多其他公开的远程仓库,常见的有Java.net Maven库和JBoss Maven库。
  输入图片说明 ####6.3.1 本地仓库####   默认情况下,不管是Windows还是linux上,每个用户在自己的用户目录下都有一个路径名为.m2/repository/的仓库目录。注意,在Linux系统中,以点(.)开头的文件或目录默认是隐藏的,可以使用ls-a命令显示隐藏的文件或目录。
  有时候,因为某些原因(例如c盘空间不够),用户会想要自定义本地仓库目录地址。这时,可以编辑文件~/.m2/settings.xml,设置localRepository元素的值为想要的仓库地址。例如:

<Settings>
    <localRepository>D:\java\repository\</localRepository>
</settings>

  这样, 该用户的本地仓库地址就设置成了D:\java\repository。 ####6.3.2 远程仓库####   对maven用户来说,每个用户只有一个本地仓库但是可以有多个远程仓库。 ####6.3.3 中央仓库####   中央仓库是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>http://repo.maven.apache.org/maven2</url>
      <layout>default</layout>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
  </repositories>

  包含这段配置的文件是所有Maven项目都会继承的超级POM。这段配置使用的id central对中央仓库进行唯一标识,其名称是Maven repository Switchborad,它使用default仓库布局。最后需要注意的是snapshots元素,其子元素enabled的值为false,表示不从该中央仓库下载快照版本的构件。
  中央仓库包含了这个世界上绝大多数流行的开源Java软件,以及源码、作者信息、SCM、信息、许可证信息等。一般来说,一个简单的Maven所需要的所有依赖构件都能从中央仓库下载到。
####6.3.4 私服####   私服是一种特殊的远程仓库,它是架设在局域网中的仓库服务,私服代理广域网上的远程仓库,供局域网内的Maven用户使用。当Maven需要下载构件的时候,它从私服请求,如果私服上不存在该构件,则从外部的远程仓库下载,缓存在私服上之后,再为Maven的下载请求提供服务。此外,一些无法从外部仓库下载到的构件也能从本地上传到私服上供大家使用(这点很重要,公司内部很多项目都是需要互通的,但是要求公司外部无法访问这些资源)。
  私服的用途:
  输入图片说明
  私服的好处类似于计算机上使用的缓存机制。详细来说有以下几点:

  • 节省自己的外网带宽:大量的对于外部仓库的重复请求会消耗很大的带宽,利用私服代理外部仓库之后,对外的重复构件下载便得以消除,即降低外网带宽的压力。
  • 加速Maven构建:不停地连接请求外部仓库是十分耗时的,使用私服可以很好的解决这一问题,当Maven只需要检查局域网内私服的数据时,构建的速度便能得到很大程度的提高。
  • 部署第三方构件:建立私服后,可以将内部构件部署到这个内部的仓库中,供内部的Maven项目使用。
  • 提高稳定性,增强控制:Maven构件高度依赖于远程仓库,因此,当Internet不稳定时,Maven的构建也会不稳定,甚至无法构建。使用私服,即使暂时没有Internet链接,Maven也能正常的使用。此外,一些私服软件(如nexus)还提供了很多额外的功能,如权限管理、REALEASE/SNAPSHOT分区等,管理员可以对仓库进行一些更高级的控制。
  • 降低中央仓库的负荷 ###6.4 远程仓库的配置###   在很多情况下,默认的远程仓库无法满足项目的需求,可能项目需要的构件存在于另外一个远程仓库中,如JBoss Maven仓库。这时,可以在POM中配置该仓库(在settings.xml中可以配置远程仓库,其作用范围不同):
     <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>
          <layout>default</layout>
        </repository>
      <repositories>

  在repositories元素中,可以包含一个或多个用repository子元素声明的远程仓库。任何一个仓库使用id必须是唯一的,由于Maven使用的中央仓库id是central,如果其他仓库声明也使用了这个id,就会覆盖中央仓库的配置。该配置中的url值指向了仓库的地址,一般来说,该地址都基于http协议,Maven用户可以浏览器中打开仓库地址浏览构件。
  配置中的release和snapshots元素用来控制Maven对于发布版构件和快照版构件的下载。关于下面的enabled元素,如果enabled为true,则表示支持其对应的release或者snapshots版本下载,反之亦然。除了enabled,它们还包含另外两个子元素updatePolicy和checksumPolicy:

<snapshots>
     <enabled>true</enabled>
     <updatePolicy>daily</updatePolicy>
     <checksumPolicy>ignore</checksumPolicy>
</snapshots>

  元素updatePolicy用来配置Maven从远程仓库检查更新的频率,默认的值是daily,表示Maven每天检查一次。其他可用的值包括:never-从不检查更新;always-每次构建都检查更新;interval:X - 每隔X分钟检查一次更新(X为任意整数)。
  元素checksumPolicy用来配置Maven检查检验和文件的策略。当构件被部署到Maven仓库中时,会同时部署对应的检验和文件。checksumPolicy用来决定当下载构件时验证校验和失败所采取的策略:当checksumPolicy的值为默认的warn时,Maven会在执行构件时输出警告信息,其他可用的值包括:fail-Maven遇到检验和错误就让构件失败;ignore-使Maven完全忽略校验和错误。
####6.4.1 远程仓库的配置####   大部分远程仓库不需要认证,有时出于安全考虑,比如某个公司自己搭建的远程仓库服务器,我们需要提供认证信息才能访问。这时,为了能让Maven访问仓库内容,就需要配置认证信息。
  配置认证信息和配置仓库信息不同,认证信息只能配置在settings.xml文件中,这是因为POM往往是被提交到代码仓库中供所有成员访问的,而setting.xml一般只放在本机。因此,在settings.xml中配置认证信息更为安全。
  假设需要一个id为my-proj的仓库配置认证信息,编辑settings.xml文件见代码如下:

<settings>
...
  <servers>
    <server>
      <id>my-proj</id>
      <username>repo-user</username>
      <password>repo-pwd</password>
    <server>
  <servers>	
...
</settings>

  settings.xml中server元素的id必须与POM中需要认证的repository元素的id完全一致。换句话说,正是这个id将认证信息与仓库配置联系在了一起。 ####6.4.2 部署至远程仓库####   私服的一大作用是部署第三方构件,包括组织内部生成的构件以及一些无法从外部仓库直接获取的构件,供其他团队使用。
  首先,需要编辑项目的pom,xml文件。配置distributionManagement元素见代码如下:

<project>
...
  <distributionManagement>
    <repository>
      <id>proj-release</id>
      <name>Proj Release Repository</name>
      <url>http://192.168.1.100/content/repositories/proj-releases</url>
    </repository>
    <snapshotRepository>
      <id>proj-snapshots</id>
      <name>Proj Snapshot Repository</name>
      <url>http://192.168.1.100/content/repositories/proj-snapshots</url>
    </snapshotRepository>		
   <distributionManagement>
...
<project>

  distributionManagement包含repository和snapshotRepository子元素,前者表示发布版本构件的仓库,后者表示快照版本的仓库。这两个元素下都需要配置id、name和url,id为该远程仓库的唯一标识,那么是为了方便阅读,关键的url表示该仓库的地址。
  往远程仓库部署构件的时候,往往需要认证。简而言之,就是需要在settings,xml中创建一个server元素,其id与仓库的id匹配,并配置正确的认证信息。无论从远程仓库下载构件,还是部署构件至远程仓库,当需要认证的时候,配置的方式是一样的。
  配置正确后,在命令行运行 mvn clean deploy ,Maven就会将项目构建输出的构件部署到配置对应的远程仓库,如果项目当前的版本是快照版本,则部署到快照版本仓库地址,否则就部署到发布版本仓库地址。 ###6.5 快照版本###   在Maven的世界中,任何一个项目或者构件都必须有自己的版本。版本的值可能是1.0.0,1.3-alpha-4,2.0,2.1-SNAPSHOT或者2.1-20091214.221414-13。其中,1.0、1.3-alpha-4和2.0是稳定的发布版本,而2.1-SNAPSHOT和2.1-20091214.221414-13是不稳定的快照版本。
  Maven为什么要区分发布版本和快照版本呢?简单的1.0.0、1.2、2.1等不就够了吗?为什么还要2.1-SNAPSHOT,甚至是长长的2.1-20091214.221414-13?试想一下这样的情况,小张在开发模块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的最新可用的快照构件,而这一切都不需要额外的手工操作。 ###6.6 从仓库解析依赖的机制###   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,LATEST和SNAPSHOT,Maven就需要基于更新远程仓库的更新策略来检查更新。在前面的仓库配置blog中,有一些配置与此有关;首先是<releases><enabled>和<snapshots><enabled>,只有仓库开启了对于发布版本的支持时,才能访问该仓库的发布版本构件信息,对于快照版本也是同理;其次要注意的是<releases>和<snapshots>的子元素<updatePolicy>,该元素配置了检查更新的频率。最后,用户还可以从命令行加入参数-U,强制检查更新,使用参数后,Maven就会忽略<updatePolicy>的配置。
  当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</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通过合并多个远程仓库及本地仓库的元数据,就能计算出基于所有仓库的latest和release分别是什么,然后再解析具体的构件。
  需要注意的是,在依赖声明中使用LATEST和RELEASE是不推荐的做法,因为Maven随时都可能解析到不同的构件,可能今天LATEST是1.3.6,明天就成了1.4.0-SNAPSHOT了,且Maven不会明确告诉用户这样的变化。当这种变化造成构建失败的时候,发现问题变得比较困难。RELEASE因为对应的是最新发布版构建,还相对可靠,LATEST就非常不可靠了,为此,Maven3不再支持在插件配置中使用LATEST和RELEASE。如果不设置插件版本,其效果就和RELEASE一样,Maven只会解析最新的发布版本构件。不过即使这样,也还存在潜在的问题。例如,某个依赖的1.1版本与1.2版本可能发布一些接口的变化,从而导致当前Maven构建失败。 当依赖的版本设为快照版本的时候,Maven也需要检查更新,这时,Maven会检查仓库元数据groupId/artifactId/version/maven-metadata.xml,如例:

<?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元素包含timestamp和buildNumber两个子元素,分别代表了这一快照的时间戳和构建号,基于这两个元素可以得到该仓库中此快照的最新构件版本实际为1.4.2-20091213.221414-13。通过合并所有远程仓库和本地仓库的元数据,Maven就能知道所有仓库中该构件的最新快照。 ###6.7 镜像###   如果仓库X可以提供仓库Y存储的所有内容,那么就可以认为X是Y的一个镜像。换句话说,任何一个可以从仓库Y获得的构件,都胡够从它的镜像中获取。举个例子,http://maven.net.cn/content/groups/public/ 是中央仓库http://repo1.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>  

  该例中,<mirrorOf>的值为central,表示该配置为中央仓库的镜像,任何对于中央仓库的请求都会转至该镜像,用户也可以使用同样的方法配置其他仓库的镜像。另外三个元素id,name,url与一般仓库配置无异,表示该镜像仓库的唯一标识符、名称以及地址。类似地,如果该镜像需认证,也可以基于该id配置仓库认证。
  关于镜像的一个更为常见的用法是结合私服。由于私服可以代理任何外部的公共仓库(包括中央仓库),因此,对于组织内部的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>

  该例中<mirrorOf>的值为星号,表示该配置是所有Maven仓库的镜像,任何对于远程仓库的请求都会被转至http://192.168.1.100/maven2/。如果该镜像仓库需要认证,则配置一个Id为internal-repository的<server>即可。为了满足一些复杂的需求,Maven还支持更高级的镜像配置:

  • <mirrorOf>*</mirrorOf> 匹配所有远程仓库。
  • <mirrorOf>external:*</mirrorOf> 匹配所有远程仓库,使用localhost的除外,使用file://协议的除外。也就是说,匹配所有不在本机上的远程仓库。
  • <mirrorOf>repo1,repo2</mirrorOf> 匹配仓库repo1和repo2,使用逗号分隔多个远程仓库。
  • <mirrorOf>*,!repo1</miiroOf> 匹配所有远程仓库,repo1除外,使用感叹号将仓库从匹配中排除。
      需要注意的是,由于镜像仓库完全屏蔽了被镜像仓库,当镜像仓库不稳定或者停止服务的时候,Maven仍将无法访问被镜像仓库,因而将无法下载构件。
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!