服务端相关 / Maven 聚合与继承

Maven 的聚合与继承

通常情况下,我们在实际开发过程中,会对项目进行模块(module)划分,来提供项目的清晰度并且能够更加方便的重用代码。但是,在这种时候,我们在构建项目的时候就需要分别构建不同的模块,Maven 的聚合特性能够将各个不同的模块聚合到一起来进行构建。而继承的特性,则能够帮助我们抽取各个模块公用的依赖、插件等,实现配置统一。

1. 聚合

这里我们以一个简单的 mall 项目作为例子。先来看一下这个项目的结构,整个项目包括 mall-core 和 mall-account 两个功能模块和 mall-aggregator 一个聚合模块。其中, mall-core 处理商城项目的核心业务逻辑, mall-account 用于管理商城的账户信息。

项目文件图示

一般来说,对于只有一个模块的项目,我们可以在该模块下直接执行 mvn clean package 命令来进行项目构建,但是,对于这样的多模块项目,我们如果要构建不同模块的话,需要分别在对应模块下执行 Maven 的相关命令,这样看起来是非常繁琐的。这个时候,Maven 的聚合特性就能够起到作用。

我们来分析一下这个项目整体的结构,首先,我们看一下 mall-aggregator 模块。这个模块作为整个工程的聚合模块,并没有实际的代码,但是其本身也是一个 Maven 项目,所以,也会存在 pom.xml 文件。那我们首先来看一下这个 pom.xml 文件有什么特点。

mall-aggregator 模块的 pom.xml 文件

我们可以看到这里面也会有相对应的 groupId , artifactId , version ,packaging 信息,其中 packaging 的值必须是 pom,否则聚合项目无法构建。我们从 modules 中可以看到整个项目包含两个模块,分别是 mall-core 和 mall-account 。通常情况下,我们将不同的模块放到聚合模块下,其中 module 的值分别对应不同模块的 artifactId 值。

在这个时候,我们 mall-aggregator 模块下,使用 mvn clean package 来进行构建,可以将两个模块同时打包完成。

[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] mall-aggregator                                                    [pom]
[INFO] mall-core                                                          [jar]
[INFO] mall-account                                                       [jar]
[INFO]
[INFO] ----------------------< com.mic:mall-aggregator >-----------------------
[INFO] Building mall-aggregator 1.0.0-SNAPSHOT                            [1/3]
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ mall-aggregator ---
[INFO]
[INFO] -------------------------< com.mic:mall-core >--------------------------
[INFO] Building mall-core 1.0.0-SNAPSHOT                                  [2/3]
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] ...
[INFO] ------------------------< com.mic:mall-account >------------------------
[INFO] Building mall-account 1.0.0-SNAPSHOT                               [3/3]
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] ...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for mall-aggregator 1.0.0-SNAPSHOT:
[INFO]
[INFO] mall-aggregator .................................... SUCCESS [  0.494 s]
[INFO] mall-core .......................................... SUCCESS [  2.152 s]
[INFO] mall-account ....................................... SUCCESS [  0.145 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  3.002 s
[INFO] Finished at: 2020-05-05T11:32:54+08:00
[INFO] ------------------------------------------------------------------------

从这次构建的过程来看,我们可以看出,Maven 会首先解析聚合模块的 pom.xml 文件,分析出有哪些模块需要构建,进而计算出一个反应堆构建顺序(Reactor Build Order),并且根据这个顺序来依次进行模块的构建。

2. 继承

2.1 继承特性

现在我们解决了同时构建不同模块同时构建的问题,但是,对于多模块项目来说,还是会有些其他的问题存在,例如,不同模块间有相同的 groupId,version ;有时候,也会需要引入相同的依赖。这个时候,如果每个模块都重复引入的话,结果就会造成冗余。作为一个遵循面向对象的程序员来讲,这样的做法显然是不合理的。

因此,Maven 也引入了类似的机制来解决这个问题,就是继承的特性。

类似于 Java 中的父类与子类,我们也可以创建一个父模块,让其他的模块作为子模块来继承该模块,从而继承父模块中声明的依赖以及配置。

对于父模块来说,只是作为配置的公共模块,是不需要代码的,而且 pom.xml 文件中的 packaging 方式也是 pom,因此,我们可以将聚合模块同时作为父模块来使用,没有必要再创建一个父模块。(当然,这里也是可以单独创建父模块的)

此时,我们查看 mall-core 或者 mall-account 的 pom.xml 文件。
图片描述

mall-core 模块的 pom.xml 文件

我们可以看到 mall-core 模块继承了父模块的坐标信息,并重新定义了 artifactId 。这时候,我们在父模块引入一个依赖,然后查看 mall-core 模块的 pom.xml 文件,会发现,在 mall-core 模块中也会引入这个依赖。但是,实际上,我们并没有在 mall-core 模块中显式的声明这个依赖。

2.2 依赖管理

其实问题并没有完全解决,并不是所有的子模块都需要引入 fastjson-1.2.49.jar 这个依赖,那要怎么办呢?

在 POM 中,我们可以在父模块中声明 dependencyManagement 元素,让子模块来继承。dependencyManagement 元素并不会实际的引入依赖,但是可以起到很好的约束依赖的作用。

首先,我们在父模块的 pom.xml 文件中声明这个元素,并加入 fastjson-1.2.49.jar 这个依赖。

<properties>
    <fastjson.version>1.2.49</fastjson.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
	</dependencies>
</dependencyManagement>

然后,我们在 mall-core 模块中添加这个依赖,但是我们并不需要再声明version。

<dependencies>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
    </dependency>
</dependencies>

这时候,我们分别查看父模块与子模块所引入的依赖,会发现,只有子模块中有引入这个依赖,而父模块中,并没有。

父模块依赖引入情况
而子模块中已经引入了这个依赖。
子模块依赖引入情况

我们通过 Maven 继承的特性,来进行依赖管理,可以更好的控制依赖的引入。而 Maven 对于插件的管理,也存在类似的元素可以使用(pluginmanagement 元素),可以做到相同的效果。

3. 反应堆

反应堆指的是整个项目中所有模块的构建结构。在本节的例子中,整个构建结构包括三个模块。反应堆不仅仅包括模块本身,还包括了这三个模块直接的相互依赖关系。

现在,我们的示例项目中,mall-core 和 mall-account 是不存在依赖关系的。父模块中模块的配置顺序如下:

<!-- 模块配置 -->
<modules>
    <module>mall-core</module>
    <module>mall-account</module>
</modules>

这里我们稍微做一下调整,用户管理模块可以提供接口给核心业务模块来调用,因此,在 mall-core 模块中引入 mall-account 依赖。重新进行项目构建。

[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] mall-aggregator                                                    [pom]
[INFO] mall-account                                                       [jar]
[INFO] mall-core                                                          [jar]
[INFO]
[INFO] ----------------------< com.mic:mall-aggregator >-----------------------
[INFO] Building mall-aggregator 1.0.0-SNAPSHOT                            [1/3]
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ mall-aggregator ---
[INFO]
[INFO] ------------------------< com.mic:mall-account >------------------------
[INFO] Building mall-account 1.0.0-SNAPSHOT                               [2/3]
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] ...
[INFO] -------------------------< com.mic:mall-core >--------------------------
[INFO] Building mall-core 1.0.0-SNAPSHOT                                  [3/3]
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] ...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for mall-aggregator 1.0.0-SNAPSHOT:
[INFO]
[INFO] mall-aggregator .................................... SUCCESS [  0.220 s]
[INFO] mall-account ....................................... SUCCESS [  1.006 s]
[INFO] mall-core .......................................... SUCCESS [  0.132 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.533 s
[INFO] Finished at: 2020-05-05T14:25:48+08:00
[INFO] ------------------------------------------------------------------------

从构建的结果来看,模块的构建顺序并不是按照我们在父模块中配置的顺序进行的,而是 Maven 在经过分析之后,生成反应堆,根据反应堆的构建顺序来进行构建的。

实际上,Maven 会根据模块间继承与依赖的关系来形成一个有向非循环图,并根据图中标记的顺序,来生成反应堆构建顺序,在构建的时候,根据这个顺序来进行构建。本节中的实例项目的有向非循环图如下:
图片描述

继承与依赖关系的有向非循环图

在这个图中,是不能出现循环的,假如我们在 mall-account 模块中也添加入 mall-core 的依赖,再进行项目构建的时候,Maven 则会报错出来,提示我们这个反应堆中存在循环引用。

[INFO] Scanning for projects...
[ERROR] [ERROR] The projects in the reactor contain a cyclic reference: Edge between 'Vertex{label='com.mic:mall-account:1.0.0-SNAPSHOT'}' and 'Vertex{label='com.mic:mall-core:1.0.0-SNAPSHOT'}' introduces to cycle in the graph com.m
ic:mall-core:1.0.0-SNAPSHOT --> com.mic:mall-account:1.0.0-SNAPSHOT --> com.mic:mall-core:1.0.0-SNAPSHOT @
[ERROR] The projects in the reactor contain a cyclic reference: Edge between 'Vertex{label='com.mic:mall-account:1.0.0-SNAPSHOT'}' and 'Vertex{label='com.mic:mall-core:1.0.0-SNAPSHOT'}' introduces to cycle in the graph com.mic:mall-
core:1.0.0-SNAPSHOT --> com.mic:mall-account:1.0.0-SNAPSHOT --> com.mic:mall-core:1.0.0-SNAPSHOT -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectCycleException

4. 小结

在本节的学习中,我们使用一个示例项目演示了 Maven 聚合与继承两个特性,以及两者之间的关系,最后介绍了 Maven 构建项目时候,反应堆构建顺序的生成,以及一些注意事项。