Maven POM的继承与组合关系

发布时间:2021-06-09 22:14
最后更新:2021-06-09 22:14
所属分类:
JVM 构建工具

Maven POM文件定义的是一个项目的基本信息和依赖信息,但是有很多项目都不是完全独立的一个项目,而是由多个项目或者多个模块组成的。Maven对于多个项目的组织,主要有继承、组合和依赖这几种。

如果不使用Maven的继承、组合等项目间关系功能,就必须在多个项目中定义大量的重复内容,这是非常消耗时间也是非常难以维护的。

一般的Java应用项目,大多都采用以下几种项目目录结构组成形式。

项目目录结构
项目目录结构

针对着几种不同的项目目录结构组成形式,可以组合使用以下Maven提供的POM关系定义。

继承

与Java中的继承类似,子POM会完全继承父POM中所有的元素,对于相同的元素,子POM会覆盖父POM中的相同元素。但是有一些元素比较特殊,在子POM中出现时,Maven会将其与父POM合并。

这些特殊的元素是:

  • dependencies
  • developers
  • contributors
  • plugins,包括其下的reports
  • resources

对于多个项目之间的目录关系来说,主要还是需要处理平级项目和嵌套项目两种关系。

平级项目继承

以上图中的平行项目关系为例,假设项目A是父级项目,项目B是子级项目,那么项目A的POM文件应该是下面这个样子。

1
2
3
4
5
6
7
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>xyz.archgrid.app</groupId>
  <artifactId>project-A</artifactId>
  <packaging>jar</packaging>
  <version>1.0</version>
</project>

首先需要明确的是,作为父级项目的项目A是不需要声明任何内容的。所有的父级项目声明都在子级项目B中,以下是项目B的POM文件内容。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<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>

可以看出,项目B的POM文件中多了<parent>元素的声明,这个元素声明了项目B与项目A之间的关系。但是因为项目B不能通过目录的层级关系定位项目A的POM文件,所以就必须使用<relativePath>元素来指定项目A的POM文件所在位置。

嵌套项目继承

嵌套项目继承就要简单多了,跟前一节一样依旧以项目A为父级项目,而项目B是嵌套在项目A的目录中的,即类似于图中深度嵌套项目关系的结构。此时项目B的POM文件内容就可以变化为以下样子。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<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>

其实这里项目B的POM文件与之前仅仅是少了一行,就是没有了<relativePath>的定义,因为这时项目B可以通过目录结构获取项目A的POM文件。

组合

组合关系与继承关系十分的不同,组合的主体是被组合的项目,这与继承实际上是反过来的。所以需要在被组合的项目中定义各个子项目,而这些子项目也可以被称为被组合项目的子模块。

而且在组合项目上使用Maven命令的时候,Maven命令的行为方式也与继承关系不同。Maven会将一个组合项目中包含的所有子项目一同处理。也就是说会将这些子项目当作一个超集项目的组成部分来处理。

平行子项目组合

这里所说的平级项目组合,主要是上图中的嵌套项目关系,这与平行项目关系不同在于在项目根目录中多了一个超集POM文件,这个文件专门用于定义超集项目的组成模块。

这里假定项目A和项目B的POM文件内容都如以下内容所示,区别仅在项目名称。

1
2
3
4
5
6
7
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>xyz.archgrid.app</groupId>
  <artifactId>project-A</artifactId>
  <packaging>jar</packaging>
  <version>1.0</version>
</project>

那么超集POM就可以像以下内容一样来定义了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<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>

这里需要注意超集POM中与其他POM文件的区别,那就是<packaging>元素的值从jar更改为了pom,这就表示这个项目是一个组合项目。超集POM中使用<modules>元素来定义组成它的各个子项目,每个子项目只需要使用<module>元素声明子项目所在的目录路径即可。对于目录是超集项目的子目录的子项目来说,可以直接书写子项目的目录名,Maven会自行定位。

如果子项目不在超集项目的目录下,或者存在的目录结构更深,那么<module>元素中可以使用相对路径或者绝对路径来声明子项目的位置。

复合子项目组合

对于图中第三种深度嵌套的项目目录结构,一般是既存在继承又存在组合的,所以POM的内容会更加复杂一点,但是只需要理清不同项目之间的关系即可。

图中第三种项目结构,项目B是超集项目的子模块,项目B继承了项目A,所以首先声明超集POM。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<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>

项目A是项目B继承的基础,所以项目A的POM文件也很简单。

1
2
3
4
5
6
7
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>xyz.archgrid.app</groupId>
  <artifactId>project-A</artifactId>
  <packaging>jar</packaging>
  <version>1.0</version>
</project>

项目B的POM文件其实就是一个比较正常的继承了项目A的POM。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<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>
在实际项目开发中,尽量不要使用这种比较复杂的目录组织结构,这种组织结构比较难以观察和管理。如果有条件还是要选择比较平坦的项目目录结构。

另外,如果必须采用采用这种复杂的项目配置方式,可以采用图中第二种项目组成方式,也就是嵌套项目关系。在这种情况下,可以在超集POM中定义更多的内容,例如项目通用的依赖和插件等。这是因为我们使用继承的主要目的是为了在不同的子项目间共享依赖、插件等配置。这样一来,项目的超集POM就会变成下面这个样子。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<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>

然后项目B的POM文件就变成了以下这个样子。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<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>

这样一来,公共依赖定义和超集POM就合并在一起了,整个项目的目录结构也就变的更加的平坦了。

本地项目间的依赖

本地项目间的依赖可以直接在<dependencies>元素中使用项目的GAV坐标定义即可,例如以下项目A的依赖定义。

1
2
3
4
5
6
7
<dependencies>
  <dependency>
    <groupId>xyz.archgrid.app</groupId>
    <artifactId>project-B</artifactId>
    <version>1.0</version>
  </dependency>
</dependencies>

需要注意的是,项目A在进行编译之前,必须要先使用mvn install命令将项目B构建打包并安装进本地库,才能够被项目A使用。


索引标签
JVM
Maven
POM
继承
组合
多项目
构建