blog/source/_posts/maven-pom-relations.md
2021-06-10 11:07:09 +08:00

250 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: Maven POM的继承与组合关系
tags:
- JVM
- Maven
- POM
- 继承
- 组合
- 多项目
- 构建
categories:
- - JVM
- 构建工具
keywords: 'maven,pom,pom.xml,module,parent,模块,项目'
date: 2021-06-09 22:14:12
---
Maven POM文件定义的是一个项目的基本信息和依赖信息但是有很多项目都不是完全独立的一个项目而是由多个项目或者多个模块组成的。Maven对于多个项目的组织主要有继承、组合和依赖这几种。<!-- more -->
如果不使用Maven的继承、组合等项目间关系功能就必须在多个项目中定义大量的重复内容这是非常消耗时间也是非常难以维护的。
一般的Java应用项目大多都采用以下几种项目目录结构组成形式。
{% oss_image maven-pom-relations/project-structure.svg 项目目录结构 750 %}
针对着几种不同的项目目录结构组成形式可以组合使用以下Maven提供的POM关系定义。
## 继承
与Java中的继承类似子POM会完全继承父POM中所有的元素对于相同的元素子POM会覆盖父POM中的相同元素。但是有一些元素比较特殊在子POM中出现时Maven会将其与父POM合并。
这些特殊的元素是:
* `dependencies`
* `developers`
* `contributors`
* `plugins`,包括其下的`reports`
* `resources`
对于多个项目之间的目录关系来说,主要还是需要处理平级项目和嵌套项目两种关系。
### 平级项目继承
以上图中的平行项目关系为例假设项目A是父级项目项目B是子级项目那么项目A的POM文件应该是下面这个样子。
{% codeblock lang:xml wrap:true %}
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>xyz.archgrid.app</groupId>
<artifactId>project-A</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
</project>
{% endcodeblock %}
首先需要明确的是作为父级项目的项目A是不需要声明任何内容的。所有的父级项目声明都在子级项目B中以下是项目B的POM文件内容。
{% codeblock lang:xml wrap:true %}
<project>
<parent>
<groupId>xyz.archgrid.app</groupId>
<artifactId>project-A</artifactId>
<version>1.0</version>
<relativePath>../projectA/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>xyz.archgrid.app</groupId>
<artifactId>project-B</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
</project>
{% endcodeblock %}
可以看出项目B的POM文件中多了`<parent>`元素的声明这个元素声明了项目B与项目A之间的关系。但是因为项目B不能通过目录的层级关系定位项目A的POM文件所以就必须使用`<relativePath>`元素来指定项目A的POM文件所在位置。
### 嵌套项目继承
嵌套项目继承就要简单多了跟前一节一样依旧以项目A为父级项目而项目B是嵌套在项目A的目录中的即类似于图中深度嵌套项目关系的结构。此时项目B的POM文件内容就可以变化为以下样子。
{% codeblock lang:xml wrap:true %}
<project>
<parent>
<groupId>xyz.archgrid.app</groupId>
<artifactId>project-A</artifactId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>xyz.archgrid.app</groupId>
<artifactId>project-B</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
</project>
{% endcodeblock %}
其实这里项目B的POM文件与之前仅仅是少了一行就是没有了`<relativePath>`的定义因为这时项目B可以通过目录结构获取项目A的POM文件。
## 组合
组合关系与继承关系十分的不同,组合的主体是被组合的项目,这与继承实际上是反过来的。所以需要在被组合的项目中定义各个子项目,而这些子项目也可以被称为被组合项目的子模块。
而且在组合项目上使用Maven命令的时候Maven命令的行为方式也与继承关系不同。Maven会将一个组合项目中包含的所有子项目一同处理。也就是说会将这些子项目当作一个超集项目的组成部分来处理。
### 平行子项目组合
这里所说的平级项目组合主要是上图中的嵌套项目关系这与平行项目关系不同在于在项目根目录中多了一个超集POM文件这个文件专门用于定义超集项目的组成模块。
这里假定项目A和项目B的POM文件内容都如以下内容所示区别仅在项目名称。
{% codeblock lang:xml wrap:true %}
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>xyz.archgrid.app</groupId>
<artifactId>project-A</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
</project>
{% endcodeblock %}
那么超集POM就可以像以下内容一样来定义了。
{% codeblock lang:xml wrap:true %}
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>xyz.archgrid.app</groupId>
<artifactId>project-root</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<modules>
<module>projectA</module>
<module>projectB</module>
</modules>
</project>
{% endcodeblock %}
这里需要注意超集POM中与其他POM文件的区别那就是`<packaging>`元素的值从`jar`更改为了`pom`这就表示这个项目是一个组合项目。超集POM中使用`<modules>`元素来定义组成它的各个子项目,每个子项目只需要使用`<module>`元素声明子项目所在的目录路径即可。对于目录是超集项目的子目录的子项目来说可以直接书写子项目的目录名Maven会自行定位。
!!! info ""
如果子项目不在超集项目的目录下,或者存在的目录结构更深,那么`<module>`元素中可以使用相对路径或者绝对路径来声明子项目的位置。
### 复合子项目组合
对于图中第三种深度嵌套的项目目录结构一般是既存在继承又存在组合的所以POM的内容会更加复杂一点但是只需要理清不同项目之间的关系即可。
图中第三种项目结构项目B是超集项目的子模块项目B继承了项目A所以首先声明超集POM。
{% codeblock lang:xml wrap:true %}
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>xyz.archgrid.app</groupId>
<artifactId>project-root</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<modules>
<module>./projectA/projectB</module>
</modules>
</project>
{% endcodeblock %}
项目A是项目B继承的基础所以项目A的POM文件也很简单。
{% codeblock lang:xml wrap:true %}
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>xyz.archgrid.app</groupId>
<artifactId>project-A</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
</project>
{% endcodeblock %}
项目B的POM文件其实就是一个比较正常的继承了项目A的POM。
{% codeblock lang:xml wrap:true %}
<project>
<parent>
<groupId>xyz.archgrid.app</groupId>
<artifactId>project-A</artifactId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>xyz.archgrid.app</groupId>
<artifactId>project-B</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
</project>
{% endcodeblock %}
!!! caution ""
在实际项目开发中,尽量不要使用这种比较复杂的目录组织结构,这种组织结构比较难以观察和管理。如果有条件还是要选择比较平坦的项目目录结构。
另外如果必须采用采用这种复杂的项目配置方式可以采用图中第二种项目组成方式也就是嵌套项目关系。在这种情况下可以在超集POM中定义更多的内容例如项目通用的依赖和插件等。这是因为我们使用继承的主要目的是为了在不同的子项目间共享依赖、插件等配置。这样一来项目的超集POM就会变成下面这个样子。
{% codeblock lang:xml wrap:true %}
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>xyz.archgrid.app</groupId>
<artifactId>project-root</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<modules>
<module>projectA</module>
<module>projectB</module>
</modules>
<dependencies>
<!-- 这里定义所有子项目共享的依赖项 -->
</dependencies>
</project>
{% endcodeblock %}
然后项目B的POM文件就变成了以下这个样子。
{% codeblock lang:xml wrap:true %}
<project>
<parent>
<groupId>xyz.archgrid.app</groupId>
<artifactId>project-root</artifactId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>xyz.archgrid.app</groupId>
<artifactId>project-B</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
</project>
{% endcodeblock %}
这样一来公共依赖定义和超集POM就合并在一起了整个项目的目录结构也就变的更加的平坦了。
## 本地项目间的依赖
本地项目间的依赖可以直接在`<dependencies>`元素中使用项目的GAV坐标定义即可例如以下项目A的依赖定义。
{% codeblock lang:xml wrap:true %}
<dependencies>
<dependency>
<groupId>xyz.archgrid.app</groupId>
<artifactId>project-B</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
{% endcodeblock %}
需要注意的是项目A在进行编译之前必须要先使用`mvn install`命令将项目B构建打包并安装进本地库才能够被项目A使用。