Maven 的继承和聚合

Maven的继承与聚合

当把Maven应用到实际项目中的时候,也需要将项目分成不同的模块,例如email和persist等模块,Maven的聚合特性能够把项目的各个模块聚合在一起构建,而Maven的继承

特性则能帮助抽取各模块相同的依赖和插件等配置,在简化POM的同时,还能促进各个模块配置的一致性。

例如persist模块负责账号数据的持久化,以XML文件的形式保存账户数据,并提供创建,读取,更新,删除操作。

persist的POM文件的,该模块的坐标为com.juvenxu.mvnbook.account:account-persist:1.0.0-SNAPSHOT,该模块的groupId和version与email模块完全一致,而artifactId也有相同的

前缀,一般来说:一个项目的子模块都应该使用相同的groupId,如果一起开发和发布则使用相同的version,此外artifactId还应该使用一致的前缀,以方便同其他项目区分。

POM中配置了一些依赖,其中dom4j是用来支持XML操作的,接下来是几个spring-framwork的依赖,与account-email中一样,他们主要用来支持依赖注入,最后一个junit依赖用来

作为单元测试,接着是build元素,它先是包含了一个testResources子元素,这是为了开启资源过滤,另外还有两个插件配置,一个配置maven-compile-plugin支持java1.5,可以

不配置插件版本,因为maven-compiler-plugin是核心插件,他的版本已经在超级POM中设定了,另外如果不配置groupId,则默认groupId为org.apache.maven.plugin.除了

maven-compiler-plugin,这里还配置了maven-resources-plugin使用UTF-8编码处理资源文件

persist模块的主代码默认位于src/main/java目录,包名是com.juvenxu.mvnbook.account.persist,该包名与account-persist的groupId com.juvenxu.mvnbook.account及artifactId

的account-persist对应,测试代码位于src/test/java/目录下,测试资源文件位于src/test/resources/目录下,上面定义要求项目classpath下有一个名为account-service.properties

的文件,并且该文件中包含了一个persist file属性,用来定义文件存储的位置,为了能够测试账户数据的持久化,在资源目录下创建属性文件account-service.properties,内容:

persist.file = ${project.build.testOutputDirectory}/persist-data.xml该文件只包含一个persist属性,表示存储账号数据的文件路径,他的值不是简单的文件路径而是

${project.build.testOutputDirectory}.这是一个Maven属性,这里读者暂时只要了解该属性表示了Maven的测试输出目录,其默认的地址为项目根目录下的target/test-classes文件

夹,也就是说在测试中使用测试输出目录下的persist-data.xml文件存储账号数据。

 聚合:

将account-email与account-persist这两个模块进行一次构建两个项目,而不是两个模块的目录下分别执行mvn命令,Maven聚合或者称为多模块这一特性就是为该需求服务的。

为了一条命令就能构建account-email和account-persist两个模块,我们需要创建一个额外的名为account-aggregator的模块,然后通过该模块构建整个项目的所有模

块,account-aggregator本身作为一个Maven项目,他必须有自己的POM,不过,同时作为一个聚合项目,其POM又有特殊地方,如下的account-aggregator的pom.xml内容:

<modelVersion>4.0.0,<aftifactId>account-aggregator,<version>1.0.0<packaging>pom,<name>Acount Aggregator,<modules><module>account-email,<module>account-persist

上述的POM使用了共同的groupId com.juvenxu.mvnbook.account,artifactId为独立的account-aggregator, 版本也和之前的模块一致,这里的第一个特殊的地方是packaging,其值

为POM,回顾之前的email和persist都没有声明packaging,即使用了默认值jar,对于聚合模块来说,其打包方式packaging的值必须为POM,否则无法构建。

XML中的name随便取,接着重要的是声明了多个module元素来实现模块的聚合,这里每个module的值都是一个当前POM的相对目录,例如此POM的路径是account-aggregator

\ pom.xml , 那么account-email与account-persist对应于目录account-aggregator\account-persist和account-aggregator\account-email两个目录下,这两个目录下各自包含了

pom.xml,src/main/java/, src/test/java等内容,离开account-aggregator也能独立构建。一般来说,为了方便快速定位内容,模块所处的目录名称应当与其actifactId一致,不过

这不是Maven的要求,用户也可以将account-email项目放到email-account/目录下,这是就要配置成<module>email-account</module>,为了用户构建项目,通常将聚合模块

放在项目目录的最顶层,其他模块作为聚合模块的子目录存在,当发现源码是,第一眼就是看到聚合模块的POM,不用从多个模块中寻找聚合模块来构建整个项目,

account-aggregator的内容仅是一个pom.xml文件,他不像其他模块有src/main/java,src/test/java等目录,聚合模块仅仅是帮助聚合其他模块的工具,本身不包含内容

除了目录采用父子目录关系外,使用平行目录结构也是可以的,而聚合模块的POM就需要改成<modules><module>../account-email, <module>../account-persist

最后对聚合模块进行mvn clean install命令,输出中Maven首先会聚合模块的POM,分析要构建的模块,并计算出反应堆构建顺序,然后按这个顺序依次构建各个模块,反应堆

是所有模块组成的一个构建结构。

继承:上面的聚合中存在很多相同的配置,例如groupId和version,spring-core等等依赖关系,还有相同的maven-compiler-plugin与maven-resources-plugin配置,所以可以使用

类继承在一定程序上消除重复,那么此时就用到了继承。

例如构建account-parent这个父类中声明一些字段和方法供子类继承,可以做到一处声明,多处使用的特点。首先需要声明父子结构,然后在父POM中声明一些配置供子POM

继承,在account-aggregator下创建一个名为account-parent的子目录,然后在该子目录下建立一个所有除account-aggregator之外模块的父模块,为此在该目录创建一个pom.xml

文件,<modelVersion>4.0.0,<groupId>com.juvenxu.mvnbook.account,<aftifactId>account-parent,<version>1.0.0-SNAPSHOT,<packaging>pom,<name>Acount Parent,

此POM中使用了和其他模块一致的groupId和version,使用了account-parent表示这是一个父模块,需要注意他的packaging为POM,这一点和聚合模块一样,作为父模块的POM

也必须把打包类型设置为POM,父模块也不需要有src/main/java之类的文件夹的了。

接着修改account-email的POM去继承它,POM的设置为<parent>com.juvenxu.mvnbook.account, <artifactId>account-parent, <version>1.0.0-SNAPSHOT,

<relativePath>../account-parent/pom.xml,   <parent>  <artifactId>account-email<name>Account Email <dependencies><build><plugins>,

上面的POM中,parent下的子元素groupId,artifactId和version指定了父模块的坐标,这三个是必须的,元素relativePath表示父模块POM的对应路径,该例中

的../account-parent/pom.xml表示父POM的位置与account-email/目录平行的account-parent/目录下,当项目构建时,Maven会首先根据relativePath检查父POM,如果找不到,

再从本地仓库查找,relativePath默认值是../pom.xml,也就是说Maven默认父POM在上一层目录下。正确的relativePath很重要,例如某个包含父子模块关系的Maven项目,由于

只迁出某一个子模块,这个时候父模块没有安装到本地仓库中,因此如果子模块没有设置正确的relativePath,Maven将无法找到父POM,这将直接导致构建失败,如果Maven

能够根据relativePath找到父POM,此时就不需要再去检查本地仓库。这个更新过的POM没有为account-email声明groupId和version,不过这并不代表account-email没有groupId

和version。实际上,这个子模块隐式的从父模块继承了这两个元素。这样就消除了一些不必要的配置,如果当子模块需要使用和父模块不一样的groupId或者version,一方面

如果完全继承模块中显示声明,对于artifactId元素来说,子模块应该显示声明,一方面如果完全继承groupId,artifactId和version会造成坐标冲突,另一方面即使使用不同的

groupId或version,同样的artifactId也容易造成混淆。

接着就将共同的依赖配置提取到父模块中,<parent><groupId>com.juvenxu.mvnbook.account,<artifactId>account-parent,<version>1.0.0,<relativePath>../account-parent/pom.xml

同时还需要把account-parent加入到聚合模块account-aggregator中,<modules><module>account-parent,<module>account-email,<module>account-persist

可继承的POM元素,以下是列出完整的列表,包括还有哪些POM元素可以被继承?

groupId:项目组ID, 项目坐标的核心元素。

version:项目版本,项目坐标的核心元素。

description:项目的描述信息

organization:项目的组织信息

dependencies:项目的依赖配置

依赖管理:

上面可继承列表包含了dependencies元素,说明依赖是会被继承的,例如account-email,spring-context.2.5.6和junit:junit:4.7,因为这些依赖配置都可以放到父模块account-

parent中,两个子模块就能移除这些依赖,简化配置。

上面的做法是可行的,我们能够确定的这两个子模块都包含上面列出的那些依赖,不过我们无法确定将来添加的子模块就一定需要这四个依赖,假设要在项目中加入一个

account-util模块,该模块只是提供一些简单的帮助工具,与springframework完全无关,难道也让他依赖spring-context,那显然不合理的。

Maven提供的dependencyManagerment元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性,在dependencyManagerment元素下的依赖声明不会

引入实际的依赖,不过它能够约束dependencies下的依赖使用,可以在account-parent中加入这个的dependencyManagerment配置

<modelVersion>4.0.0<groupId>com.juvenxu.mvnbook.account<artifactId>account-parent<version>1.0.0-SNAPSHOT<packaging>pom<name>Account Parent

<properties>,<springframework.version>2.5.6<junit.version>4.7<dependencyManagement><dependencies><dependency>,<groupId>org.springframework<artifactId>spring-core

<version>${springframework.version}<dependency><groupId>org.springframework<artifactId>spring-beans<version>${springframework.version}多个dependency以此类推进行设

置,从上面可以看出将springframework和junit依赖的版本以Maven变量的形式提取出来,这里使用dependencyManagement声明的依赖既不给account-parent引入依赖,也不会

给他的子模块引入依赖,不过这段配置是会被继承的,现在修改account-email的POM如下:

<properties><javax.mail.version>1.4.1,<greenmail.version>1.3.1b<dependencies><dependency><groupId>org.springframework,<artifactId>spring-core,

<dependency>org.springframework<artifactId>spring-beans 这样去依次设置,这里的POM的依赖配置简单一些,所有的springframework依赖只配置了groupId和artifactId,省去了

version,而junit依赖不仅省去了version,还省去了依赖范围scope,这些信息可以省略是因为account-email继承了account-parent中的dependencyManagement配置,完整的依赖

声明已经包含在父POM中,子模块只需要配置简单的groupId和artifactId就能获得对应的依赖信息,从而引入正确的依赖。主要原因是父POM中使用dependencyManagement声明

依赖能够统一项目范围中依赖的版本,当依赖版本在父POM中声明之后,子模块在使用依赖的时候就无须声明版本,也就不会发生多个子模块使用依赖版本不一致的情况,可以

降低冲突的几率,如果子模块不声明依赖的使用,即使依赖已经在父POM的dependencyManagement中生命了,也不会产生任何实际的效果,如account-persist的POM里面没有

声明spring-context-support,那么该依赖就不会被引入,这就是dependencyManagement的灵活性所在。之外,还有import的依赖范围,因为该范围的依赖只在

dependencyManagement元素下才有效果,使用该范围的依赖通常指向一个POM,作用是将目标POM中的dependencyManagement配置导入并合并到当前POM的

dependencyManagement元素中,例如想要在另外一个模块中使用与这个完全一样的dependencyManagement配置,除了复制配置或者继承两种方式之外,还可以使用import范

围依赖将这一配置导入,<dependencyManagement><dependencies><dependency><groupId>com.juvenxu.mvnbook.account<artifactId>account-parent,<version>1.0-

SNAPSHOT,<type>pom,<scope>import,这里的依赖的type值为pom,import范围依赖由于其特殊性,一般指向打包模块为POM的模块,如果有多个项目,他们使用的依赖版本

都是一致的,则就可以定义一个使用dependencyManagement专门管理依赖的POM,然后在各个项目中导入这些依赖管理配置。

插件管理:

Maven除了提供dependencyManagement元素帮助管理依赖,类似的,Maven也提供了pluginManagement元素帮助管理插件,在该元素中配置的依赖不会造成实际的插件调用行

为,在POM中配置了真正的plugin元素,而且其groupId和artifactId与pluginManagement中配置的插件匹配时,pluginManagement的配置才会影响实际的插件行为。

例如maven-source-plugin配置了,将其jar-no-fork目标绑定到了verify生命周期阶段,以生成源码包,如果一个项目有很多模块,并且需要得到所有这些模块的源码包,那么很

显然,为所有模块重复类似的插件配置不是最好的方法,这时更好的方法是在父POM中使用pluginManagement配置插件。

<build><pluginManagement><plugins><plugin><groupId>org.apache.maven.plugins,<argifactId>maven-source-plugin,<version>2.1.1,<executions><execution><id>

attach-sources,<phase>verify<goals><goal>jar-no-fork,而子模块需要这样配置:<build><plugins><plugin><groupId>org.apache.maven.plugins,<artifactId>maven-source-plugin

子模块声明使用了maven-source-plugin插件,同时又继承了父模块的pluginManagement配置,两者基于groupId和artifactId匹配合并。如果子模块不需要使用父模块中

pluginManagement配置的插件,可以尽管将其忽略,如果子模块需要不同的插件配置,则可以自行配置以覆盖父模块的pluginManagement配置。有了pluginManagement元素

account-email和account-persist的POM也能得以简化了,他们都配置了maven-compiler-plugin和maven-resources-plugin,可以将这两个插件的配置移到account-parent的

pluginManagement元素中。此时这两个模块可以完全的移除关于maven-compiler-plugin和maven-resources-plugin的配置,但他们仍可以享受这两个插件的服务,前一个插件

是开启jave 5编译的支持,后一个是使用UTF-8编码处理资源文件,这涉及了Maven很多机制,首先,内置的插件绑定关系将两个插件绑定到了account-email和account-persist

的生命周期上,其次,超级POM为这两个插件声明了版本,最后account-parent中的pluginManagement对这两个插件的行为进行了配置。所以将配置移到父POM的

pluginManagement元素中,即使各个模块对于同一插件的具体配置不尽相同,也应当使用父POM的pluginManagement元素统一声明插件的版本,甚至可以要求将所有用到的插

件的版本在父POM的pluginManagement元素中声明,子模块使用插件时不配置版本信息,可以统一插件的版本。

聚合与继承的关系:前者是方便快速构建项目,后者是消除重复配置,共同点是两个POM的packaging的必须是POM,同时聚合模块与继承模块都是除了POM之外没有实际内容

有时往往会发现一个POM既是聚合POM,也是继承中的父POM,融合使用继承和聚合是没有问题的,例如account-aggregator和account-parent合并成一个新的account-parent。

该POM的打包方式为POM,他包含一个modules元素,表示用来聚合account-persist和account-email两个模块,还包含了properties,dependencyManagement和

pluginManagement,相应的,account-email和account-persist的POM配置也要做微小的修改,本来account和他们位于同级目录,因此需要使用值为../account-parent/pom.xml

的relativePath元素,现在新的account-parent在上一次目录,这是Maven默认能识别的父模块位置,因此不在需要配置relativePath。

约定由于配置:这是Maven最核心的设计理念之一,约定可以减少配置,在Ant中需要做的就是清除构建目录,编译代码,复制依赖至目标目录。最后打包,这是构建最基本的事

情,不过此时还是需要写很多的XML配置,源码什么,编译目标和分发目录时什么等等,用户还需要记住各种Ant任务命令,如delete,mkdir,javac和jar,同样是事情,Maven

只需要一个最简单的POM,<project><modelVersion> 4.0.0<groupId>com.juvenxu-mvnbook,<artifactId>my-project,<version>1.0  , 同时为了获得这样简洁的配置,用户需要付出

一定的代价,那就是遵循Maven的约定,Maven会假设用户的项目是这样的:源码目录src/main/java,编译输出目录为target/classes/,打包方式为jar,包输出目录为target/,遵循约

定虽然会损失一定的灵活性,用户不能随便安排目录,但是

却能减少配置,如果没有约定,则10个项目可能使用10种不同的项目目录结构,而有了Maven的约定大家都知道什么目录放什么地方。此外,Ant的自定义目录名称不同,Maven

则在命令行暴露的用户接口是统一的,例如mvn clean install这样的命令可以用来构建几乎任何的Maven项目。如果不想要遵守约定,但是个性往往意味着牺牲通用性,意味着

增加无谓的复杂度,例如Maven允许使用你自定义源码目录。<project><modelVersion>4.0.0<groupId>com.juvenxu.mvnbook<artifactId>my-project<version>1.0<build>

<sourceDirectory>src/java,此时源码目录就成了src/java了,往往造成交流问题,除非你在处理遗留代码,并且没有办法更改原来的目录结构,此时只能让Maven妥协了。

前面多次提到的超级POM,任何一个Maven项目都隐式的继承自该POM,这有点类似java类隐式继承与Object类,因此大量超级POM的配置都会被所有Maven项目继承,

这些配置就成为Maven所提倡的约定。

对于POM3,超级POM在文件$MAVEN_HOME/lib/maven-model-builder-X.X.X.jar中的org.apache/maven/model/pom-4.0.0.xml路径下,超级POM的内容如下:

首先超级POM定义了仓库和插件仓库,两者的地址都是中央仓库http://repol.maven.org/maven2,并且关闭了SNAPSHOT的支持,这也解释了为什么Maven默认就可以按需要

从中央仓库下载构件。这里依次定义了项目的主输出目录,主代码输出目录等等各个参数设定,大家可以自行去观察超级POM了解插件的具体版本。

反应堆:在一个多模块的Maven项目中,反应堆是指所有模块组成的一个构建结构,对于单模块的项目,反应堆就是其本身,但对于多模块项目来说,反应堆就包含了各个模块

之间继承和依赖的关系,从而能够自动计算出合理的模块构建顺序。

反应堆的构建顺序:将account-aggregator包含email,persist,parent,上述可以看出反应堆,依次是aggregator,parent,email,persist,结果中看到parent跑到了email之前,

这是因为考虑到了继承和依赖关系,因为email和persist都依赖于parent,所有parent就必须先于另外两个模块构建,如果出现A依赖于B,而B也依赖于A时,Maven就会报错。

裁剪反应堆:用户想要仅仅构建完整反应堆中的某些个模块,Maven提供很多的命令行选项支持裁剪反应堆,输入mvn -h可以看到这些选项:例如-am,--also-make(同时构建所

列模块的依赖模块),-amd, -also-make-dependents(同时构建依赖于所列模块的模块),pl, --projects<arg>(构建指定的模块,模块间用逗号分隔),rf -resume-from<arg>(从指定

的模块回复反应堆),接着默认情况从account-aggregator执行 mvn clean install, pl选项指定构建某几个模块,指令:$mvn clean install -pl account-email,account-persist

使用-am选项可以同时构建所有列模块的依赖模块 命令:$mvn clean install -pl account-email -am。使用-amd选项可以同时构建依赖于所有模块的模块 命令:

$mvn clean install -pl account-parent -amd(包括:email和persist)。 rf 选项可以在完整的反应堆构建顺序基础上指定从哪个模块开始构建,命令:$mvn clean install -rf account-

email,在完整的反应堆构建顺序中,account-email位于第三,之后只有account-persist,因此只会得到email和persist的反应堆,

最有在pl -am, pl-amd的基础上,还能应用-rf参数,以对裁剪后的反应堆在此裁剪,$mvn clean install -pl account-parent -amd -rf account-email.该命令中的-pl 和- amd参数会

裁剪一个account-parent,account-email和account-persist的反应堆,在此基础上, -rf参数指定从account-email参数构建,因此会得到如下反应堆 email和persist,通过这四种

参数,可以帮助我们跳过无需构建的模块,从而加速构建,因为项目庞大,模块特别多的时候,这种效果会异常明显。





























 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值