劝退提醒:不了解 maven 坐标机制,没有使用过 maven 插件,不了解 maven 插件命令的,可以关闭文章了。

我们经常在项目中引入包,使用 <dependency> 标签,然后填写包的坐标信息, Maven 就可以帮我们引入该 jar 包。当本地存在该 jar 包时,就从本地引入该 jar 包,否则就从远程仓库引入。

这背后的解析机制是什么呢?

依赖解析机制

依赖解析的背后机制可以概括如下:

  1. 依赖范围是 system,maven 就会从本地加载该 jar 包,完成构建。

  2. 正常解析依赖坐标,先去本地仓库找,找到就完成构建。

  3. 本地找不到,并且显示的指定了版本信息,就去远程仓库中遍历,找到并下载解析。

  4. 如果依赖的版本信息并没有指定,而是使用 RELEASE或者 LATEST,他会找到远程仓库的元数据和本地的元数据进行合并,然后计算出真正的版本号。元数据指的就是仓库中 groupId/artifactId/maven-metadata.xml文件,例如我们打开 guava 包的元数据。元数据位置就在 com.google.guava/guava 包下,打开可以看到如下信息:

<metadata modelVersion="1.1.0">
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<versioning>
  <latest>30.1.1-jre</latest>
	<release>30.1.1-jre</release>
  <versions>
  <version>r03</version>
	<version>r05</version>
	<version>r06</version>
	<version>r07</version>
	<version>r08</version>
	<version>r09</version>
	...
  <version>30.1-android</version>
	<version>30.1-jre</version>
	<version>30.1.1-android</version>
	<version>30.1.1-jre</version>
  </versions>
  <lastUpdated>20210319161151</lastUpdated>
</versioning>
</metadata

上面的信息展示了 latest,release 对应的版本号,以及 guava 包的历代版本号,以及最近一次更新时间。例如我们引入 guava,并使用 latest 版本,maven 就会合并元数据,然后计算出 latest 对应的版本号,然后从本地仓库找到改版本的 guava,没有则去远程仓库下载。

  1. 依赖的版本是 SNAPSHOT,则同样获取远程元数据与本地合并,计算版本信息,获取对应版本的包。

  2. 如果依赖版本为时间戳的快照版本,就会先转换成非时间戳的快照版本,然后去解析下载。

  3. maven 3 中如果不指定 version,则默认解析使用 RELEASE 版本。

插件解析机制

我们常用的 maven 插件命令都是 mvn dependency:tree mvn flyway:migrate,诸如此类的,他们的格式都是 mvn 插件前缀:目标

为了方便用户使用和配置插件,maven 不需要用户提供完整的坐标信息,就能解析得到正确的插件。那么问题来了,maven 是怎么确定插件的坐标和版本的呢?例如 mvn dependency:tree 他执行了什么插件,插件坐标版本信息是什么?

插件的解析机制和依赖解析机制基本一致,不同的是远程仓库不一样。配置插件的远程仓库地址需要使用 <pluginRepository> 标签配置,如下:

<pluginRepositories>
  <pluginRepository>
    <id></id>
    <name></name>
    <url></url>
  </pluginRepository>
</pluginRepositories>

除了 <pluginRepositories><pluginRepository>不同,其他的与依赖的仓库配置信息一致。

默认的 groupId

在 pom 文件在红配置插件信息的时候,如果插件是 maven 官方的插件(groupId 为 org.apache.maven.plugins),就可以省略 groupId 的配置。如下是官方的 clean 插件配置,没有指定 groupId,但是解析坐标的时候会带上。

       <plugins>
            <plugin>
                <artifactId>maven-clean-plugin</artifactId>
                <version>3.1.0</version>
            </plugin>
        </plugins>

在插件配置中,不推荐省略 groupId 的写法,但这与我们使用 maven 插件命令有关。

省略版本信息

maven 的核心插件都在超级 pom 中显示的声明了具体的版本信息,因此当这些插件未指定版本信息的时候,就会使用超级 pom 中指定的插件版本。

如果是非核心插件,且未指定版本信息。在 maven2 中默认解析至 latest,maven3 默认解析至 release,具体的解析过程与依赖解析一致,都是根据合并计算元数据,得到具体的版本信息。

插件前缀解析

我们使用命令 mvn dependency:tree 插件前缀是 dependency,我们是如何根据这个前缀信息得到该插件的完整坐标呢?

这就需要结合上面的知识了,首先存在一个保存 groupId 和 artifactId 对应关系的文件 maven-metadata.xml,该文件存在 groupId / maven-metadata.xml。由于上面的 dependency 是官方插件,因此 groupId 为 org.apache.maven.plugins ,我们去远程仓库找到该文件,如下图。

image.png

我们可以找到插件前缀 dependency 对应的 articfactId 是 maven-dependency-plugin,因此插件的完整坐标可以确定了。

获取 groupId

这里还有个问题,因为我们是提前知道 dependency 是官方的插件,可以推出他的 groupId ,但是 maven 是怎么知道它的 groupId 呢?

maven 的主要插件都在 [https://repo1.maven.org/maven2/org/apache/maven/plugins](https://repo1.maven.org/maven2/org/apache/maven/plugins)[https://repository.codehaus.org/org/code-haus/mojo](https://repository.codehaus.org/org/code-haus/mojo)下,他们对应的 groupId 分别是 org.apache.maven.pluginsorg.codehaus.mojo。maven 解析插件的时候就会默认使用这两个 groupId 去匹配,检查 org/apache/maven/plugins/maven-metadata.xml,org/codehaus.momjo/maven-metadata.xml 文件,判断是否有匹配的插件前缀,如果有则获取对应的坐标信息,完成解析。

倘若想使用的插件是第三方的,就可以通过配置 setting.xml 文件,让 maven 也检查其他 groupId 上的仓库 metadata.xml 文件。

<setting>
  <pluginGroups>
    <pluginGroup>第三方仓库 groupId</pluginGroup>  
  </pluginGroups>
</setting>

示例

解析 dependency:tree 命令

  1. 先使用默认的和配置的第三方的 groupId 去找到对应的 maven-metadata.xml文件,然后检查是否包含 dependency 前缀信息。

  2. 包含前缀信息,获取对应 articfactId,不包含则使用下一个 groupId 的 maven-metadata.xml 文件。如果所有都获取不到,则报错。

  3. 获取到 articfactId 之后根据上面 获取版本信息,即可得到完整的坐标,完成解析执行。