[关闭]
@lemonguge 2017-09-13T14:49:45.000000Z 字数 7601 阅读 651

聚合与继承

Maven


Maven 的聚合特性能够把项目的各个模块聚合在一起构建,而 Maven 的继承特性则能帮助抽取各模块相同的依赖和插件等配置,在简化 POM 的同时,还能促进各个模块配置的一致性。

假如当前有三个模块,finance-core 核心模块,finance-rest 接口模块,finance-service 服务模块,接下来都会以这三个模块进行讲解。


聚合

Maven 聚合可以同时构建多个模块,首先需要构建一个额外的名为 finance-aggregator 的模块,作为一个 Maven 项目,它必须要有自己的 POM,不过,同时作为一个聚合项目,其 POM 又有特殊的地方。

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4. <groupId>com.juvenxu.mvnbook.account</groupId>
  5. <artifactId>finance-aggregator</artifactId>
  6. <version>1.0.0-SNAPSHOT</version>
  7. <packaging>pom</packaging>
  8. <name>Finance Aggregator</name>
  9. <modules>
  10. <module>finance-core</module>
  11. <module>finance-rest</module>
  12. <module>finance-service</module>
  13. </modules>
  14. </project>

有以下值得注意的地方:

  1. packaging 值为 pom,对于聚合模块来说,其打包方式 packaging 的值必须为 pom,否则就无法构建。
  2. modules 元素这是实现聚合的最核心的配置,可以声明任意数量的module元素来实现模块的聚合,每个 module 的值都是一个当前 POM 的相对目录。上面的配置文件中,finance-aggregator的 POM 目录路径为 D:\github\finance-aggregator\pom.xml,那么 finance-core 对应的目录为 D:\github\finance-aggregator\finance-core,finance-rest 对应的目录为 D:\github\finance-aggregator\finance-rest,finance-service 对应的目录为 D:\github\finance-aggregator\finance-service。
  3. 为了方便快速定位内容,module 元素内为各模块所处的目录名称,而不是 artifactId,但是建议与 artifactId 一致,不过这不是 Maven 的必须要求。用户也可以将 finance-rest 项目放到 finance-web 目录下,这时聚合的配置就需要相应地改成 <module>finance-web</module>

为了方便用户构建项目,通常将聚合模块放在项目目录的最顶层,其他模块则作为聚合模块的子目录存在,这样当用户得到源码的时候,第一眼发现的就是聚合模块的 POM,不用从多个模块中去寻找聚合模块来构建整个项目。聚合模块与其他模块的目录结构并非一定要是父子关系,如果使用平行目录结构,聚合模块的POM也需要做相应的修改,以指向正确的模块目录,如果 finance-service 项目与 finance-aggregator 为平行目录,finance-rest 项目到 finance-web 目录下,那么配置如下:

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4. <groupId>com.juvenxu.mvnbook.account</groupId>
  5. <artifactId>finance-aggregator</artifactId>
  6. <version>1.0.0-SNAPSHOT</version>
  7. <packaging>pom</packaging>
  8. <name>Finance Aggregator</name>
  9. <modules>
  10. <module>finance-core</module>
  11. <!-- artifactId为finance-rest,但目录名为finance-web -->
  12. <module>finance-web</module>
  13. <!-- 与聚合模块为平行目录 -->
  14. <module>../finance-service</module>
  15. </modules>
  16. </project>

Maven会首先解析聚合模块的POM、分析要构建的模块、并计算出一个反应堆构建顺序(Reactor Build Order),然后根据这个顺序依次构建各个模块。反应堆是所有模块组成的一个构建结构。


继承

在多个模块的 POM 配置中有很多相同的依赖,这就是重复,重复往往就意味着更多的劳动和更多的潜在的问题。在面向对象世界中,程序员可以使用类继承在一定程度上消除重复,在 Maven 的世界中,也有类似的机制能让我们抽取出重复的配置,这就是 POM 的继承。

创建 POM 的父子结构,然后在父 POM 中声明一些配置供子 POM 继承,以实现 “一处声明,多处使用” 的目的。在 finance-aggregator 的项目目录下创建一个名为 finance-parent 的子目录,在该子目录下创建一个 pom.xml 文件,代码如下:

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4. <groupId>com.juvenxu.mvnbook.account</groupId>
  5. <artifactId>finance-parent</artifactId>
  6. <version>1.0.0-SNAPSHOT</version>
  7. <packaging>pom</packaging>
  8. <name>Finance Parent</name>
  9. </project>

该POM十分简单,它使用了与其他模块一致的 groupIdversion,使用的 artifactIdfinance-parent 表示这是一个父模块。需要特别注意的是,它的 packagingpom,这一点与聚合模块一样,作为父模块的 POM,其打包类型也必须为 pom

由于父模块只是为了帮助消除配置的重复,因此它本身不包含除 POM 之外的项目文件,也就不需要 src/main/java/ 之类的文件夹了,当前的项目目录结构:

- - -
finance-aggregator - -
finance-aggregator pom.xml -
finance-aggregator finance-parent -
finance-aggregator finance-parent pom.xml
finance-aggregator finance-core -
finance-aggregator finance-core src
finance-aggregator finance-core pom.xml
finance-aggregator finance-rest -
finance-aggregator finance-rest src
finance-aggregator finance-rest pom.xml
finance-aggregator finance-service -
finance-aggregator finance-service src
finance-aggregator finance-service pom.xml

有了父模块,就需要让其他模块来继承它。首先将 finance-core 的 POM 修改如下,见代码:

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4. <parent>
  5. <groupId>com.juvenxu.mvnbook.account</groupId>
  6. <artifactId>finance-parent</artifactId>
  7. <version>1.0.0-SNAPSHOT</version>
  8. <!-- 默认为../pom.xml -->
  9. <relativePath>../finance-parent/pom</relativePath>
  10. <parent>
  11. <artifactId>finance-core</artifactId>
  12. <name>Finance Parent</name>
  13. <dependencies>
  14. <!-- too many ignore -->
  15. </dependencies>
  16. <build>
  17. <plugins>
  18. <!-- too many ignore -->
  19. </plugins>
  20. </build>
  21. </project>

上述 POM 中使用 parent 元素声明父模块,parent 下的子元素 groupIdartifactIdversion 指定了父模块的坐标,这三个元素是必须的。元素 relativePath 表示父模块 POM 的相对路径,该例中的 ../finance-parent/pom.xml 表示父 POM 的位置在与 finance-core/ 目录平行的 finance-parent/ 目录下。当项目构建时,Maven 会首先根据 relativePath 检查父 POM,如果找不到,再从本地仓库查找。relativePath 的默认值是 ../pom.xml,也就是说,Maven默认父 POM 在上一层目录下。

可以发现 finance-core 模块的 groupIdversion 并没有声明,实际上,这个子模块隐式地从父模块继承了这两个元素,这也就消除了一些不必要的配置。如果遇到子模块需要使用和父模块不一样的 groupId 或者 version 的情况,那么用户完全可以在子模块中显式声明。对于 artifactId 元素来说,子模块应该显式声明。

最后,同样还需要把 finance-parent 加入到聚合模块 finance-aggregator 中:

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4. <groupId>com.juvenxu.mvnbook.account</groupId>
  5. <artifactId>finance-aggregator</artifactId>
  6. <version>1.0.0-SNAPSHOT</version>
  7. <packaging>pom</packaging>
  8. <name>Finance Aggregator</name>
  9. <modules>
  10. <module>finance-core</module>
  11. <module>finance-rest</module>
  12. <module>finance-service</module>
  13. <module>finance-parent</module>
  14. </modules>
  15. </project>

可继承的POM元素

在上文我们看到,groupIdversion 是可以被继承的,那么还有哪些 POM 元素可以被继承呢?以下是一个完整的列表,并附带了简单的说明:

POM 元素 简要说明
groupId 项目组 ID,项目坐标的核心元素
version 项目版本,项目坐标的核心元素
properties 自定义的Maven属性
dependencies 项目的依赖配置
dependencyManagement 项目的依赖管理配置
distributionManagement 项目的仓库部署配置
repositories 项目的仓库配置
build 包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等
description 项目的描述信息
organization 项目的组织信息
inceptionYear 项目的创始年份
url 项目的URL地址
developers 项目的开发者信息
contributors 项目的贡献者信息
issueManagement 项目的缺陷跟踪系统信息
ciManagement 项目的持续集成系统信息
scm 项目的版本控制系统信息
mailingLists 项目的邮件列表信息
reporting 包括项目的报告输出目录配置、报告插件配置等

依赖管理

Maven 提供了 dependencyManagement 元素帮助管理依赖,类似地,Maven 也提供了 pluginManagement 元素帮助管理插件。在该元素中配置的依赖不会造成实际的插件调用行为,当 POM 中配置了真正的 plugin 元素,并且其 groupIdartifactIdpluginManagement 中配置的插件匹配时,pluginManagement 的配置才会影响实际的插件行为。

如果一个项目中有很多子模块,并且需要得到所有这些模块的源码包,那么很显然,为所有模块重复类似的插件配置不是最好的办法。这时更好的方法是在父 POM 中使用 pluginManagement 配置插件,见代码:

  1. <project>
  2. <!-- 在父POM中配置插件管理 -->
  3. <build>
  4. <pluginManagement>
  5. <plugins>
  6. <plugin>
  7. <groupId>org.apache.maven.plugins</groupId>
  8. <artifactId>maven-source-plugin</artifactId>
  9. <version>3.0.1</version>
  10. <executions>
  11. <execution>
  12. <id>attach-sources</id>
  13. <phase>verify</phase>
  14. <goals>
  15. <goal>jar-no-fork</goal>
  16. </goals>
  17. </execution>
  18. </executions>
  19. </plugin>
  20. </plugins>
  21. </pluginManagement>
  22. </build>
  23. </project>

当子模块需要生成源码包的时候,只需要如下简单的配置:

  1. <project>
  2. <!-- 继承了pluginManagement后的插件配置 -->
  3. <build>
  4. <plugins>
  5. <plugin>
  6. <groupId>org.apache.maven.plugins</groupId>
  7. <artifactId>maven-source-plugin</artifactId>
  8. </plugin>
  9. </plugins>
  10. </build>
  11. </project>

子模块声明使用了 maven-source-plugin 插件,同时又继承了父模块的 pluginManagement 配置。如果子模块不需要使用父模块中 pluginManagement 配置的插件,可以尽管将其忽略。如果子模块需要不同的插件配置,则可以自行配置以覆盖父模块的 pluginManagement 配置。

当项目中的多个模块有同样的插件配置时,应当将配置移到父 POM 的 pluginManagement 元素中。即使各个模块对于同一插件的具体配置不尽相同,也应当使用父 POM 的 pluginManagement 元素统一声明插件的版本。甚至可以要求将所有用到的插件的版本在父 POM 的 pluginManagement 元素中声明,子模块使用插件时不配置版本信息,这么做可以统一项目的插件版本,避免潜在的插件不一致或者不稳定问题,也更易于维护。


聚合与继承的关系

多模块 Maven 项目中的聚合与继承其实是两个概念,其目的完全是不同的。前者主要是为了方便快速构建项目,后者主要是为了消除重复配置。

对于聚合模块来说,它知道有哪些被聚合的模块,但那些被聚合的模块不知道这个聚合模块的存在。

对于继承关系的父 POM 来说,它不知道有哪些子模块继承于它,但那些子模块都必须知道自己的父 POM 是什么。

如果非要说这两个特性的共同点,那么可以看到,聚合POM与继承关系中的父POM的packaging都必须是pom,同时,聚合模块与继承关系中的父模块除了POM之外都没有实际的内容。

在现有的实际项目中,读者往往会发现一个 POM 既是聚合 POM,又是父 POM,这么做主要是为了方便。一般来说,融合使用聚合与继承也没有什么问题,在微服务项目中,并不建议这么做。


反应堆

在一个多模块的 Maven 项目中,反应堆(Reactor)是指所有模块组成的一个构建结构。对于单模块的项目,反应堆就是该模块本身,但对于多模块项目来说,反应堆就包含了各模块之间继承与依赖的关系,从而能够自动计算出合理的模块构建顺序

模块的构建次序可能与它们在聚合模块中的声明顺序不一致

当出现模块A 依赖于B,而 又依赖于 A 的情况时,Maven就会报错。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注