Maven的聚合特性能够把项目的各个模块聚合在一起构建,而Maven的继承特性则能帮助抽取各模块相同的依赖和插件等配置。
一. 聚合
Maven聚合(或者称为多模块),是为了能够使用一条命令就构建多个模块,例如已经有两个模块,分别为account-email,account-persist,我们需要创建一个额外的模块(假设名字为account-aggregator,然后通过该模块,来构建整个项目的所有模块,accout-aggregator本身作为一个Maven项目,它必须有自己的POM,不过作为一个聚合项目,其POM又有特殊的地方,看下面的配置:
<project 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> <artifact>account-aggregator</artifact> <version>1.0.0-SNAPSHOT</version> <packaging>pom</packaging> //note:要使用聚合的话必须为pom <name>Account Aggregator</name> <modules> <module>account-email</module> <module>account-persist</module> </modules> </project>
上面有一个特殊的地方就是packaging,其值为pom,如果没有声明的话,默认为jar,对于聚合模块来说,其打包方式必须为pom,否则无法构建。
modules里的每一个module都可以用来指定一个被聚合模块,这里每个module的值都是一个当前pom的相对位置,本例中account-email、account-persist位于account-aggregator目录下,当三个项目同级的时候,上面的两个module应该分别为../account-email和../account-persist
举例:
(1)创建一个父项目a
(2)创建一个子项目b
点击父项目的pom文件,为他添加model
一路next,最终finish
刷新一下项目a,就可以看见下面有个子项目b
note: 项目a中的除pom.xml文件其它都可以事先删除掉。
同理,再建立一个子项目c
(3)到父模板下面执行mvn clean install
Reactor Summary: [INFO] [INFO] a 0.0.1-SNAPSHOT ................................... SUCCESS [ 0.303 s] [INFO] b .................................................. SUCCESS [ 1.893 s] [INFO] c 0.0.1-SNAPSHOT ................................... SUCCESS [ 0.757 s] [INFO] ------------------------------------------------------------------------
说明:Maven会首先解析聚合模块的pom分析要构建的模块、并计算出一个反应堆构建顺序(Reactor Build Order),然后根据这个顺序依次构建各个模块。上面的红色名字部分正是pom种的name字段的值,因此,配置一个有意义的名字比较好。
二. 继承
2.1 使用说明
maven中的继承类似于java项目中的继承。
maven中的继承的实现步骤:
- 建立父工程:父工程的打包方式为pom
- 在父工程的pom.xml中编写依赖:
<dependencyManagement> //如果是父工程,需要写dependencyManagement,否则普通工程直接不用写这个 <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> </dependencyManagement>
- 子类:在子类中添加parent的配置
<parent> <groupId>com.test</groupId> <artifactId>base</artifactId> <version>0.0.1-SNAPSHOT</version> <relativePath>../base/pom.xml</relativePath> //代表的从子类pom.xml到父类pom.xml的相对路径 </parent>
note: 找相对路径的办法:先找到当前的pom.xml,可以通过alt+enter快捷键定位到计算机中的存储位置(有一个黄色的link可以点击直接跳转到计算机的存储位置)。
然后子pom需要先跳出来,跳到本项目同级别的父项目的位置,假设是base,然后再进入到base里面寻找Pom,因此相对路径就是../base/pom.xml
- 子类:需要声明,使用哪些父类的依赖
知道了大致的继承创建过程后,下面举一个例子来进一步理解。
2.2 举例
1. 创建父项目base
第一个勾选了允许在创建maven项目的时候就能够修改打包方式。并且,创建以后的东西还可以省去处理pom.xml文件之外的文件。
2. 在父类的pom.xml中添加依赖
<dependencyManagement> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> </dependencyManagement>
3. 创建子工程,且配置Pom.xml
因为两者的groupId和version一样,因此,无需在子模块中继续声明了。
<parent> <groupId>com.test</groupId> <artifactId>base</artifactId> <version>0.0.1-SNAPSHOT</version> <relativePath>../base/pom.xml</relativePath> </parent> <artifactId>sub</artifactId> <packaging>jar</packaging> <name>sub</name> <url>http://maven.apache.org</url>
note:先保证子类没有junit 4.12版本
4. 刷新子类,发现Junit4.12并没有在maven depenciy下拉菜单中,是因为没有继承么?实际上这是正确的。倘若父类有很多子类没有的东西,那岂不是父类所有的jar包都需要展示在子类的依赖包的下拉菜单中了。那么如何证明继承成功呢?有两个办法:
(1)查看子类的effective pom.xml,其中就包含了从子类继承来的所有jar包。发现是有junit4.12的,只是pom.xml中没有而已。
(2)直接在子类的pom中显示的声明需要依赖junit 4.12的版本,只需要给出groupID和architeID就可以了,会自动从父类中继承4.12版本的。刷新后,就会在maven depency下来菜单中出现了。
2.3 可继承的pom元素
- groupId:项目组ID,项目坐标的核心元素
- version:项目版本,项目坐标的核心元素
- description:项目的描述信息
- organnization:项目的组织信息
- inceptionYear:项目的创始年份
- url:项目的URL地址
- developers:项目的开发者信息
- contributors:项目的贡献者信息
- distributionManagement:项目的部署配置
- issueManagement:项目的缺陷跟踪系统信息
- ciManagement:项目的集成信息
- scm:项目的版本控制系统信息
- mailingLists:项目的邮件列表信息
- properties:自定义的Maven属性
- dependencies:项目的依赖配置
- dependencyManagement:项目的依赖管理配置
- repositories:项目的仓库配置
- build:包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等
- reporting:包括项目的报告输出目录配置,报告插件配置等。
三. 聚合与继承的关系
多模块中的聚合与继承其实是两个概念,其目的是完全不同的,前者主要是为了方便快速构建项目,后者主要是为了消除重复配置。
对于聚合模块来说,它知道有哪些被聚合的模块,但那些被聚合的子模块不知道这个聚合模块的存在。
对于继承关系的父POM来说,它不知道哪些子模块继承于它,但那些子模块都必须知道自己的父POM是什么。
在现有的实际项目中,往往会发现一个POM即是聚合POM,又是父POM,这么做主要是为了方便。一般来说,融合使用聚合与继承也没什么问题。
四. 约定优于配置
Maven默认的源码目录是:src/main/java但是用户也可以自己指定源码目录,如下:
<project> <modelVersion>4.0.0</modelVersion> <groupId>com.juvenxu.mvnbook<groupId> <artifactId>my-project</artifactId> <version>1.0</version> <build> <sourceDirectory>src/java</sourceDirectory> </build> </project>
但是一般还是不要自定义了,不好管理。
五. 反应堆
反应堆的构建顺序
在一个多模块的Maven项目中,反应堆(Reactor)是指所有模块组成的一个构建结构。对于单模块的项目,反应堆就是该模块本身,对于多模块来说,反应堆就包含了各模块之间继承与依赖的关系,从而能够自动计算出合理的模块构建顺序。
例如:
<modules> <module>account-email</module> <module>account-persist</module> <module>account-parent</module> </modules>
以上,构建顺序不一定是顺序去读取POM的顺序,当一个模块依赖于另外一个模块,Maven会先去构建被依赖模块,并且Maven中不允许出现循环依赖的情况,就是,当出现模块A依赖于B,而B又依赖于A的情况时,Maven就会报错。
裁剪反应堆
一般来说,用户会选择构建整个项目或者选择构建单个的模块,但有些时候,用户会想要仅仅构建完整反应堆中的某些个模块。换句话说,用户需要实时地剪裁反应堆。
Maven提供很多命令行选项支持裁剪反应堆,输入mvn -h可以看到这些选项:
- -am, --also-make:同时构建所列模块的依赖模块
- -amd -also-make-dependents:同时构建依赖于所列模块的模块
- -p,--project
- -rf -resume-from
默认情况从account-aggregator执行mvn clean install会得到如下完整的反应堆:
[INFO]-----------------------------------------------------
[INFO]Reactor Build Order:
[INFO]
[INFO]Account Aggregator
[INFO]Account Parent
[INFO]Account Email
[INFO]Account Persist
[INFO]
[INFO]-----------------------------------------------------
可以使用-pl选项指定构建某几个模块,如运行如下命令:
$ mvn clean install -p account-email,account-persist
得到的反应堆为:
[INFO]-----------------------------------------------------
[INFO]Reactor Build Order:
[INFO]
[INFO]Account Email
[INFO]Account Persist
[INFO]
[INFO]-----------------------------------------------------
六. 补充
多模块的创建涉及到了聚合和继承。也可以通过新建一个maven(simple)工程作为父工程,删除src等东西,只留下Pom.xml,然后点击父项目,右键new-->maven module方式来创建子Module,这样继承和聚合关系就自动生成了。记得刷新一下。
参考文献
《Maven实战》