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