@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 又有特殊的地方。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.juvenxu.mvnbook.account</groupId><artifactId>finance-aggregator</artifactId><version>1.0.0-SNAPSHOT</version><packaging>pom</packaging><name>Finance Aggregator</name><modules><module>finance-core</module><module>finance-rest</module><module>finance-service</module></modules></project>
有以下值得注意的地方:
packaging 值为 pom,对于聚合模块来说,其打包方式 packaging 的值必须为 pom,否则就无法构建。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。module 元素内为各模块所处的目录名称,而不是 artifactId,但是建议与 artifactId 一致,不过这不是 Maven 的必须要求。用户也可以将 finance-rest 项目放到 finance-web 目录下,这时聚合的配置就需要相应地改成 <module>finance-web</module>。为了方便用户构建项目,通常将聚合模块放在项目目录的最顶层,其他模块则作为聚合模块的子目录存在,这样当用户得到源码的时候,第一眼发现的就是聚合模块的 POM,不用从多个模块中去寻找聚合模块来构建整个项目。聚合模块与其他模块的目录结构并非一定要是父子关系,如果使用平行目录结构,聚合模块的POM也需要做相应的修改,以指向正确的模块目录,如果 finance-service 项目与 finance-aggregator 为平行目录,finance-rest 项目到 finance-web 目录下,那么配置如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.juvenxu.mvnbook.account</groupId><artifactId>finance-aggregator</artifactId><version>1.0.0-SNAPSHOT</version><packaging>pom</packaging><name>Finance Aggregator</name><modules><module>finance-core</module><!-- artifactId为finance-rest,但目录名为finance-web --><module>finance-web</module><!-- 与聚合模块为平行目录 --><module>../finance-service</module></modules></project>
Maven会首先解析聚合模块的POM、分析要构建的模块、并计算出一个反应堆构建顺序(Reactor Build Order),然后根据这个顺序依次构建各个模块。反应堆是所有模块组成的一个构建结构。
在多个模块的 POM 配置中有很多相同的依赖,这就是重复,重复往往就意味着更多的劳动和更多的潜在的问题。在面向对象世界中,程序员可以使用类继承在一定程度上消除重复,在 Maven 的世界中,也有类似的机制能让我们抽取出重复的配置,这就是 POM 的继承。
创建 POM 的父子结构,然后在父 POM 中声明一些配置供子 POM 继承,以实现 “一处声明,多处使用” 的目的。在 finance-aggregator 的项目目录下创建一个名为 finance-parent 的子目录,在该子目录下创建一个 pom.xml 文件,代码如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.juvenxu.mvnbook.account</groupId><artifactId>finance-parent</artifactId><version>1.0.0-SNAPSHOT</version><packaging>pom</packaging><name>Finance Parent</name></project>
该POM十分简单,它使用了与其他模块一致的 groupId 和 version,使用的 artifactId 为 finance-parent 表示这是一个父模块。需要特别注意的是,它的 packaging 为 pom,这一点与聚合模块一样,作为父模块的 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 修改如下,见代码:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.juvenxu.mvnbook.account</groupId><artifactId>finance-parent</artifactId><version>1.0.0-SNAPSHOT</version><!-- 默认为../pom.xml --><relativePath>../finance-parent/pom</relativePath><parent><artifactId>finance-core</artifactId><name>Finance Parent</name><dependencies><!-- too many ignore --></dependencies><build><plugins><!-- too many ignore --></plugins></build></project>
上述 POM 中使用 parent 元素声明父模块,parent 下的子元素 groupId、artifactId 和 version 指定了父模块的坐标,这三个元素是必须的。元素 relativePath 表示父模块 POM 的相对路径,该例中的 ../finance-parent/pom.xml 表示父 POM 的位置在与 finance-core/ 目录平行的 finance-parent/ 目录下。当项目构建时,Maven 会首先根据 relativePath 检查父 POM,如果找不到,再从本地仓库查找。relativePath 的默认值是 ../pom.xml,也就是说,Maven默认父 POM 在上一层目录下。
可以发现 finance-core 模块的 groupId 和 version 并没有声明,实际上,这个子模块隐式地从父模块继承了这两个元素,这也就消除了一些不必要的配置。如果遇到子模块需要使用和父模块不一样的 groupId 或者 version 的情况,那么用户完全可以在子模块中显式声明。对于 artifactId 元素来说,子模块应该显式声明。
最后,同样还需要把 finance-parent 加入到聚合模块 finance-aggregator 中:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.juvenxu.mvnbook.account</groupId><artifactId>finance-aggregator</artifactId><version>1.0.0-SNAPSHOT</version><packaging>pom</packaging><name>Finance Aggregator</name><modules><module>finance-core</module><module>finance-rest</module><module>finance-service</module><module>finance-parent</module></modules></project>
在上文我们看到,groupId 和 version 是可以被继承的,那么还有哪些 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 元素,并且其 groupId 和 artifactId 与 pluginManagement 中配置的插件匹配时,pluginManagement 的配置才会影响实际的插件行为。
如果一个项目中有很多子模块,并且需要得到所有这些模块的源码包,那么很显然,为所有模块重复类似的插件配置不是最好的办法。这时更好的方法是在父 POM 中使用 pluginManagement 配置插件,见代码:
<project><!-- 在父POM中配置插件管理 --><build><pluginManagement><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-source-plugin</artifactId><version>3.0.1</version><executions><execution><id>attach-sources</id><phase>verify</phase><goals><goal>jar-no-fork</goal></goals></execution></executions></plugin></plugins></pluginManagement></build></project>
当子模块需要生成源码包的时候,只需要如下简单的配置:
<project><!-- 继承了pluginManagement后的插件配置 --><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-source-plugin</artifactId></plugin></plugins></build></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就会报错。