Maven实战(三)- Maven仓库

Maven实战(三)- Maven仓库

1.Maven仓库概念

Maven仓库是一种用于存储和管理Maven构建项目所需依赖的地方。它是Maven构建系统的核心组件之一,用于下载、缓存和共享项目依赖的构件(如jar、war、pom等)。

我们在开发项目过程中,可能会有多个Maven项目。这些项目可能用到了同一个构件例如junit-4.10.jarslf4j-api-1.7.2.jar等,如果我们在没有需要这些构件的项目中都放置一份重复的junit-4.10.jar或者slf4j-api-1.7.2.jar显然是不好的。这样既浪费磁盘空间,又难以统一管理。

得益于依赖坐标机制,任何Maven项目使用任何一个构件的方式都是完全相同的。在此基础上,Maven可以在指定位置上统一存储所有Maven项目共享的构件,在需要这些构件的时候,声明其依赖坐标,Maven会自动根据坐标找到仓库中的构件,并使用它们。

2.仓库布局

Maven仓库布局是指:任何构件都有其唯一的坐标(包括groupId、‌artifactId、‌version和packaging),‌根据这个坐标,‌Maven可以确定构件在仓库中的存储路径。

例如,org.slf4j:slf4j-api:1.7.32这一依赖,其对应的仓库路径为org/slf4j/slf4j-api/1.7.32/slf4j-api-1.7.32.jar,不难发现,依赖坐标和仓库路径的对应关系为groupId/artifactId/version/artifactId-version.packaging

下面看一段Maven的源码,并结合实例来理解。

    // 文件路径分隔符
		private static final char PATH_SEPARATOR = '/';
		// groupId分隔符
    private static final char GROUP_SEPARATOR = '.';
		// 构件分隔符
    private static final char ARTIFACT_SEPARATOR = '-';

    public String getId()
    {
        return "default";
    }

    public String pathOf( Artifact artifact )
    {
        ArtifactHandler artifactHandler = artifact.getArtifactHandler();

        StringBuilder path = new StringBuilder( 128 );
				// 步骤1
        path.append( formatAsDirectory( artifact.getGroupId() ) ).append( PATH_SEPARATOR );
      	// 步骤2
        path.append( artifact.getArtifactId() ).append( PATH_SEPARATOR );
      	// 步骤3
        path.append( artifact.getBaseVersion() ).append( PATH_SEPARATOR );
      	// 步骤4
        path.append( artifact.getArtifactId() ).append( ARTIFACT_SEPARATOR ).append( artifact.getVersion() );
				// 步骤5
        if ( artifact.hasClassifier() )
        {
            path.append( ARTIFACT_SEPARATOR ).append( artifact.getClassifier() );
        }

        if ( artifactHandler.getExtension() != null && artifactHandler.getExtension().length() > 0 )
        {
            path.append( GROUP_SEPARATOR ).append( artifactHandler.getExtension() );
        }

        return path.toString();
    }

org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout#pathOf该方法是用来根据构件信息生成其在仓库中的路径。下面我们以上面的构件org.slf4j:slf4j-api:1.7.32为例,讲解上述方法的步骤。

<dependency>
   <groupId>`org.slf4j</groupId>
   <artifactId>slf4j-api</artifactId>
   <version>1.7.32</version>
</dependency>

其groupId=org.slf4j,artifactId=slf4j-api,version=1.7.32,packaging=jar。

  1. formatAsDirectory( artifact.getGroupId() )会将 groupId 中的分隔符转换为文件路径分隔符。所以org.slf4j将会被转换为org/slf4j,并在其后加上一个文件路径分隔符,即最终为org/slf4j/
  2. 加上artifactId,并在其后加上一个文件路径分隔符,结果为org/slf4j/slf4j-api/
  3. 加上version,并在其后加上一个文件路径分隔符,结果为org/slf4j/slf4j-api/1.7.32/
  4. 加上artifactId、ARTIFACT_SEPARATOR(构件分隔符)、version,结果为org/slf4j/slf4j-api/1.7.32/slf4j-api-1.7.32
  5. 如果构件有classifier元素,则会加上构件分隔符和classifier。
  6. 最后检查构件的extension,也就是扩展名。而extension是从artifactHandler中获取的,artifactHandler是由项目的packaging决定的。该例的packaging=jar,所以拼接后的结果为org/slf4j/slf4j-api/1.7.32/slf4j-api-1.7.32.jar

3.仓库分类

对于Maven来说,仓库只分为两类:本地仓库和远程仓库。当Maven根据坐标寻找构件的时候,他首先会查看本地仓库,如果本地仓库存在此构件,则直接使用;如果本地仓库不存在,或者需要查看是否有更新版本,则会去远程仓库查找,找到了,则会下载至本地仓库再使用。如果都没找到,Maven会报错。

3.1.本地仓库

默认情况下,Maven会在用户目录下创建一个路径名为.m2/repository/的仓库目录。如果用户需要自定义本地仓库目录地址,可以通过打开配置文件setting.xml,设置localRepository元素的值为本地仓库的目录。

<localRepository>/Users/ahao/maven/apache-maven-3.6.3/repo</localRepository>

一个构件只有在本地仓库中,才能由其他Maven项目使用。如何将构件导入本地仓库呢?一种就是从远程仓库下载至本地仓库,另一种就是通过mvn clean install,将本地项目的构件输出文件安装至本地仓库中。

3.2.远程仓库

远程仓库是一个存储构件(如jar、war、pom等)的远程服务器,Maven通过网络与该仓库交互来下载所需的构件。远程仓库可以是中央仓库、私服(私有仓库)、其他公共库。

实际上,根据仓库的供应方,远程仓库可以分为中央仓库、私服(私有仓库)、其他公共库。而从构件的角度,还可以分为快照仓库(Snapshot),发布仓库(Release), 公共仓库(Public ,不区分Snapshot还是Release)。

Maven仓库
本地仓库
远程仓库
中央仓库
私服
其他公共库

3.3.中央仓库

最原始的本地仓库里面空空如也,所有需要至少一个可用的远程仓库,才能在执行Maven命令的时候下载需要的构件。而中央仓库就是Maven内部默认的一个远程仓库,在Maven的安装文件中自带了中央仓库的配置。从官网下载目录中,安装Maven的源码文件,打开src/main/resources/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</id>表示中央仓库的唯一标识,并且其名称为Central Repository(中央仓库);<url>https://repo.maven.apache.org/maven2</url> 表示仓库的地址;<layout>default</layout>表示使用默认的布局,也就是 2.仓库布局 介绍的布局;<snapshots><enabled>false</enabled></snapshots>表示不会从中央仓库下载快照版本的构件。

3.4.私服

私服是一种特殊的远程仓库,一般是搭建在局域网内的仓库服务,私服代理了广域网上的远程仓库,供局域网内的用户使用。当Maven需要下载构件的时候,先查看本地仓库,如果本地仓库没有,再请求私服,如果私服也没有,则会从外部的远程仓库进行下载,并缓存在私服上。此外,一些无法从外部仓库下载到的构件,也能从本地上传至私服上供内部用户使用,如图所示。

在这里插入图片描述

搭建私服的好处:

  • 节省外网带宽。 大量的对于外部仓库的重复请求会消耗很大的带宽,利用私服之后,对外的构件下载便得以消除,降低外网带宽压力。
  • 提高构建速度。 不停地连接请求外部仓库是十分耗时的,但是Maven的一些内部机制(如快照更新检查)要求Maven在执行构建的时候不停地检查远程仓库数据。私服可以在本地网络环境中托管构件,使得构建的速度明显提高。
  • 私有构件发布。 私服允许团队将自己的构件上传到仓库,并与其他成员共享和使用。
  • 提高稳定性。 Maven构建高度依赖于远程仓库,所以依托于Internet的稳定性。私服在局域网内部运行,不受外部网络环境的影响。
  • **安全性和访问控制。 **私服可以提供访问控制和身份验证机制,只允许授权用户访问和下载构件。

4.远程仓库的配置

很多情况下,默认的中央仓库无法满足项目的需求,可能需要从另一个远程仓库中拉取,例如JBoss Maven仓库。可以在POM中配置该仓库,如下所示。

<project>
  ...
  <repositories>
    <repository>
      <id>repositoryId</id>
      <url>https://org.repository.com/repo/path</url>
      <layout>default</layout>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
  </repositories>
  ...
</project>

<repositories>元素中,通过声明多个<repository>元素来配置多个远程仓库。和 3.3.中央仓库 的元素一致,<releases>表示开启下载发布版本的构件。

对于releases和snapshots来说,除了enabled,还有updatePolicy和checksumPolicy两个元素:

<snapshots>
  <enabled>false</enabled>
  <updatePolicy>daily</updatePolicy>
  <checksumPolicy>warn</checksumPolicy>
</snapshots>

元素 updatePolicy 用来配置Maven从远程仓库检查更新的频率,默认值为daily。可选值:always - 每次构件都检查更新;daily - 每天更新一次;interval:XXX - 其中XXX表示每隔XXX分钟检查一次更新;never - 从不检查更新。

元素 checksumPolicy 用来配置Maven检查效验和文件的策略。当构件被上传至Maven仓库时,会同时上传构件的效验和文件。在下载构件的时候,Maven会验证效验和文件,如果效验和验证失败,怎么办?这个时候就取决于checksumPolicy元素的值:fail - 遇到效验和错误就直接构件失败;warn - 输出警告信息;ignore - 忽略错误信息。

5.远程仓库认证

大部分远程仓库无须认证就可以访问,但是有时候处于安全方面的考虑,需要提供认证信息才可以访问远程仓库。

配置认证信息和配置仓库信息不用,仓库信息可以直接配置在POM文件中,但是认证信息必须配置在 settings.xml 文件中。这是因为POM往往是被提交到代码仓库中供所有成员访问的,而 settings.xml 一般是放在本机,所以 settings.xml 中配置认证信息更加安全。

假设需要为仓库id为my-repository的仓库配置认证信息,则需要设置如下元素。

<settings>
  	<servers>
        <server>
          <id>my-repository</id>
          <username>user_name</username>
          <password>123456</password>
        </server>
    </servers>
</settings>

如上所示,username元素表示用户名,password元素表示用户密码。

6.部署构件至远程仓库

私服的一大作用就是部署内部或者第三方的构件,一般包括组织内部私有的构件以及一些无法从外部仓库直接获取的构件。所以无论是日常开发中生成的构件,还是正式版发布的构件,都需要部署到仓库中,供组织内的其他成员使用。

部署项目生成的构件至仓库,需要在pom.xml中配置distributionManagement元素。

<project>
  ...
  <distributionManagement>
		<repository>
       <id>release_repo</id>
       <name>release repository</name>
       <url>http//192.12.12.12/repository/release</url>
    </repository>
    <snapshotRepository>
       <id>snapshot_repo</id>
       <name>snapshot repository</name>
       <url>http//192.12.12.12/repository/snapshot</url>
    </snapshotRepository>
  </distributionManagement>
  ...
</project>

在上述配置中,distributionManagement元素中包含了repository和snapshotRepository两个元素。前者表示发布版本的构件仓库,后者表示快照版本的构件仓库。一般来说,往远程仓库部署构件时需要认证,而配置认证的方式在上节已经介绍过了。

7.从仓库解析依赖

当本地仓库没有依赖构件时,Maven会自动从远程仓库下载;

1) 当依赖范围为system的时候,Maven会直接从本地文件系统解析构件。

2) 根据依赖坐标计算仓库路径后,尝试直接从本地仓库寻找构件,查找到则解析成功。

3) 如果本地仓库不存在对应构件,如果依赖的版本是显式的发布版本构件,如1.2、2.7 等,则遍历所有的远程仓库,下载并解析使用。

4) 如果依赖的版本是RELEASE或者LATEST,则基于更新策略读取所有远程仓库的元数据groupld/artifactld/maven-metadata.xml,将其与本地仓库的对应元数据合并后,计算出RELEASE
或者LATEST真实的值,然后基于这个真实的值检查本地和远程仓库,如步骤2)和3)。

5) 如果依赖的版本是SNAPSHOT,则基于更新策略读取有远程仓库的元数据groupId/artifactId/version/maven-metadata.xml,将其与本地仓库的对应元数据合并后,得到
最新快照版本的值,然后基于该值检查本地仓库,或者从远程仓库下载。
6) 如果最后解析得到的构件版本是时间戳格式的快照,如1.44.1-20091104.121450-121,
则复制其时间戳格式的文件至非时间戳格式,如SNAPSHOT,并使用该非时间戳格式的构件。

当依赖的版本不明晰的时候,如RELEASE、LATEST和SNAPSHOT,Maven就需要基于更新远程仓库的更新策略来检查更新。在4.远程仓库的配置中,有一些配置与此有关:首先是<releases><enabled><snapshots><enabled>,只有仓库开启了对于发布版本的支持时,才能访问该仓库的发布版本构件信息,对于快照版本也是同理;其次要注意的是<releases><snapshots>的子元素<updatePolicy>。该元素配置了检查更新的频率,每检查更新、永远检查更新、从不检查更新、自定义时间间隔检查更新等。

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

<?xml version="1.0" encoding="UTF-8"?>
<metadata>
  <groupId>org.ahao.project</groupId>
  <artifactId>base-project</artifactId>
  <versioning>
    <latest>1.4-SNAPSHOT</latest>
    <release>1.3</release>
    <versions>
      <version>1.0-SNAPSHOT</version>
      <version>1.1-SNAPSHOT</version>
      <version>1.2-SNAPSHOT</version>
      <version>1.3</version>
      <version>1.4-SNAPSHOT</version>
    </versions>
    <lastUpdated>20240722095316</lastUpdated>
  </versioning>
</metadata>

该XML文件列出了仓库中存在的该构件所有可用的版本,同时latest元素指向了这些版本中最新的那个版本,该例中是1.4-SNAPSHOT。而release元素指向了这些版本中最新的发布版本,该例中是1.3。Maven通过合并多个远程仓库及本地仓库的元数据,就能计算出基于所有仓库的latest和release分别是什么,然后再解析具体的构件。

当依赖的版本设为快照版本的时候,Maven也需要检查更新。这时,Maven会检查仓库元数据groupId/artifactld/version/maven-metadata.xml,如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<metadata modelVersion="1.1.0">
  <groupId>org.ahao.project</groupId>
  <artifactId>base-project</artifactId>
  <versioning>
    <snapshot>
      <timestamp>20240722.095316</localCopy>
      <buildNumber>3</buildNumber>
    </snapshot>
    <lastUpdated>20240722095316</lastUpdated>
  </versioning>
  <version>1.0-SNAPSHOT</version>
</metadata>

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

最后,仓库元数据并不是永远正确的,有时候当用户发现无法解析某些构件,或者解析得到错误构件的时候,就有可能是出现了仓库元数据错误,这时就需要手工地,或者使用工具(如Nexus)对其进行修复。

8.镜像

如果仓库A可以提供仓库B的所有内容,那么就可以认为A是B的一个镜像。例如,阿里云的Maven公共仓库 https://maven.aliyun.com/repository/public 是Maven中央仓库 https://repo.maven.apache.org/maven2 的镜像。由于地理位置的原因,阿里云的公共仓库能够提供比Maven中央仓库更快的服务。因此配置 Maven 使用阿里云公共仓库来替代Maven中央仓库。

<settings>
  ...
		<mirrors>
        <mirror>
            <id>aliyun</id>
            <name>aliyun maven</name>
          	<!-- central表示该配置为中央仓库的镜像 -->
            <mirrorOf>central</mirrorOf>
            <url>https://maven.aliyun.com/repository/public</url>
        </mirror>
    </mirrors>
  ...
</settings>

该例中,<mirrorOf>的值为central,表示该配置为中央仓库的镜像,任何对于中央仓库的请求都会转至该镜像,用户也可以使用同样的方法配置其他仓库的镜像。以下是内部元素说明:

  • mirror: 镜像仓库。
  • id: 镜像仓库的唯一标识符。
  • name: 镜像仓库名称。
  • url: 镜像仓库的地址。
  • mirrorOf: 镜像匹配(条件)。

其中<mirrorOf>支持更加复杂的配置:

  • <mirrorOf>central</mirrorOf>:匹配中央仓库。

  • <mirrorOf>*</mirrorOf>:星号 * 表示匹配所有的远程仓库。

  • <mirrorOf>external:*</mirrorOf>:匹配所有的远程,使用localhost的除外、使用files://协议的除外。

  • <mirrorOf>repo1,repo2</mirrorOf>:匹配仓库repo1和repo2。

  • <mirrorOf>*,!repo1</mirrorOf>:匹配所有的远程仓库,但仓库repo1除外。使用感叹号表示将仓库从匹配中移除。

    注:内容源于《Maven实战》(许晓斌著)

  • 17
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值