我们会想要一次构建两个项目,而不是到两个模块的目录下分别执行mvn命令。Maven聚合(或者称为多模块)这一特性就是为该需求服务的。
为了能够使用一条命令就能构建a和b两个模块,我们需要创建一个额外的名为c的模块,然后通过该模块构建整个项目的所有模块。c本身作为一个Maven项目,他必须要有自己的POM,不过,同时作为一个聚合项目,其POM又有特殊的地方。如下c的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.test</groupId>
<artifactId>c</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>c</name>
<modules>
<module>a</module>
<module>b</module>
</modules>
</project>
上述POM依旧使用了账户注册服务共同的groupId com.test,artifactId为独立的c,版本也与其他两个模块一致,为1.0.0-SNAPSHOT.这里的第一个特殊的地方为packaging,即使用了默认值jar。对于聚合模块来说,其打包方式packaging的值必须为pom,否则就无法构建。
POM的name字段是为了给项目提供一个更容易阅读的名字。元素modules,这是实现聚合的最核心的配置。用户可以通过在一个打包方式为pom的Maven项目中声明任意数量的module元素来实现模块的聚合。这里每个module的值都是一个当前POM的相对目录,譬如该例中,c的POM的路径为D:\...\workspace\c\pom.xml,那么a就对应了目录D:\...\workspace\a\,而b对应于目录D:\...\workspace\b\。这两个目录各自包含了pom.xml、src/main/java/、src/test/java/等内容,离开c也能独自构建。
一般来说,为了方便快速定位内容,模块所处的目录名称应当与其artifactId一致,不过这不是Maven的要求,用户也可以将a项目放到d/目录下。这时,聚合的配置就需要相应的改成<module>d</module>。
为了方便用户构建项目,通常将聚合模块放在项目目录的最顶层,其他模块则作为聚合模块的子目录存在,这样当用户得到源码的时候,第一眼发现的就是聚合模块的POM,不用从多个模块中去寻找聚合模块来构建整个项目。
c的内容仅是一个pom.xml文件,他不像其他模块那样有src/main/java、src/test/java等目录。这也是容易理解的,聚合模块仅仅是帮助聚合其他模块构建的工具,他本身并无实质的内容。
关于目录结构还需要注意的是,聚合模块与其他模块的目录结构并非一定要是父子关系,例如平行的目录结构。
如果使用平行目录结构,聚合模块的POM也需要做相应的修改,以指向正确的模块目录:
<modules>
<module>../a</module>
<module>../b</module>
</modules>
从聚合模块运行mvn clean install命令时,Maven会首先解析聚合模块的POM、分析要构建的模块、并计算出一个反应堆构建顺序,然后根据这个顺序依次构建各个模块。反应堆是所有模块组成的一个构建结构。
控制台输出中显示的各模块的名称,而不是artifactId,这也解释了为什么要在POM中配置合理的name字段,其目的是让Maven的构建输出更清晰。输出的最后是一个项目构建的小结报告,包括各个模块构建成功与否、花费的时间,以及整个构建花费的时间、使用的内存等。