《Maven实战》阅读总结(四)聚合与继承

(七)聚合与继承

软件设计人员往往会采用各种方式对软件划分模块,以得到更清晰的设计及更高的重用性。
Maven聚合特性,将项目的各个模块聚合在一起构建
Maven继承特性,抽取各模块相同的依赖和插件等配置。

聚合

聚合项目,顾名思义,就是将多个项目聚合在一起。
通常情况下,聚合项目的目录结构如下

|-parent             <!-- 父工程是一个Maven项目 -->
  |-parent_POM       <!-- 父工程拥有自己的POM文件,packaging元素值为pom,在modules中添加module元素实现项目聚合 -->
  |-sub1             <!-- 子工程通常位于父工程目录下,该目录通常与子工程artifactId相同,但不是必须 -->
    |-sub1_POM       <!-- 父工程POM中module的值为子工程POM所在目录(相对目录,相对于父工程目录) -->
  |-sub2             <!--  -->
    |-sub2_POM       <!--  -->

通常情况下,子工程位于父工程目录下,且子工程目录名与其artifactId相同,但实际情况由module的值所决定,即module值的路径能定位到子工程POM文件即可,对目录命名与布局没有绝对要求。
同时父工程只需pom文件即可,因为聚合模块仅仅是帮助聚合其他模块构建工具,它本身并无实质的内容。
如下图
clipboard.png

执行聚合项目声明周期时,Maveng会首先解析模块的POM、分析要构建的模块、并计算出一个反应堆构建顺序(Reactor Build Order),然后根据这个顺序依次构建各个模块。反应堆是所有模块组成的一个构建结构。

继承

Maven的继承特性可以抽取重复的配置。在父POM中声明一些配置供子类POM继承,实现"一处声明,多处使用"。
同聚合模块一样,继承父工程是一个maven项目,拥有自己的POM文件,packaging类型为pom。由于父工程只是帮助消除重复配置,因此其本身不包含除POM之外的项目文件。

1)继承声明

在父工程POM文件中声明复用依赖或插件等配置,安装到本地仓库即可由其他子项目继续。
在子项目POM文件中声明父工程。
同时子项目可省略groupId和version,因为子项目POM会隐式继承父项目POM的groupId和version。

...
<parent>
    <groupId>...</groupId>              <!-- 必须,声明父工程的groupId -->
    <artifactId>...</artifactId>        <!-- 必须,声明父工程的artifactId -->
    <version>...</version>              <!-- 必须,声明父工程的version -->
    <relativePath>...</relativePath>    <!-- 可省略,默认为../pom.xml,声明父工程的POM文件所在路径 -->
</parent>

<artifactId>...<artifactId>    <!-- 省略groupId和version,隐式继承父POM的groupId和version -->
<name>...</name>
...

子项目构建时,Maven会根据relativePath检查父POM,如果找不到,再从仓库查找。
正确设置relativePath非常重要,如果团队新成员从源码库检出一个包含父子模块关系的Maven项目,由于只关心子项目,当其直接到子项目下执行构建,由于本地仓库没有安装父项目,将直接导致构建失败。如果Maven能够根据relativePath找到父POM,它就不需要再去检查本地仓库。

2)可继承的POM元素
groupId项目组ID,项目坐标的核心元素
version项目版本,项目坐标的核心元素
description项目的描述信息
organization项目的组织信息
inceptionYear项目的创始年份
url项目的URL地址
developers项目的开发者信息
contributors项目的贡献者信息
distributionManagement项目的部署配置
issueManagement项目的缺陷跟踪系统信息
ciMananagement项目的持续集成系统信息
scm项目的版本控制系统信息
mailingLists项目的邮件列表信息
properties自定义的Maven属性
dependencies项目的依赖配置
dependencyManagement项目的依赖管理配置
repositories项目的仓库配置
build包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等
reporting包括项目的报告输出目录配置、报告插件配置等
3)依赖管理

dependencyManagement 依赖管理配置,在该元素中声明的依赖不会被引入,而是起到约束并简化子项目依赖的作用,子项目会继承其声明的依赖配置,如version,scope。springboot项目的父工程应该是配置了该依赖管理配置,实现项目依赖无需填写版本号。

import依赖范围,只在dependencyManagement元素中生效,可以导入其他项目的dependencyManagement配置

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>...</groupId>
            <artifactId>...</artifactId>
            <version>...</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        ...
    </dependencies>
</dependencyManagement>
4)插件管理

类似于dependencyManagement元素,pluginManagement元素用于管理插件,在该元素中配置的依赖不会造成实际的插件调用方式,可以理解为dependencyManagement 声明了插件依赖的配置,当其子类声明该插件依赖时,可复用该配置,起到约束和简化作用。

Ps:什么情况下可以用到依赖管理和插件管理,当一些依赖或插件依赖只有部分子模块需要时,应该由子模块自身去声明引入依赖并配置,而这些配置都是重复的,通过依赖管理和插件管理可以实现配置的复用,并且不会给其他模块引入不必要的依赖。

聚合和继承的关系

聚合概念方便快速构建项目
不同点对于聚合模块,它需要知道哪些被聚合的模块,但被聚合的模块不知道这个聚合模块的存在
继承概念消除重复配置
不同点对于继承关系的父POM,它不知道哪些模块继承于它,但那些子模块必须知道自己的父POM
相同点聚合模块和继承关系中的父POM的packaging都必须是pom。同时,它们除了POM文件外都没有实际内容

预定优于配置

标准很重要,Web应用开发基于HTTP协议、Java屏蔽大部分操作系统差异,实现跨平台、所有语言都支持XML。
如果没有约定,10个项目就可能使用10种不同的项目目录结构,这意味着交流学习成本增加。
遵循Maven的约定

源码目录src/main/java
编译输出目录target/classes
打包方式jar
包输出目录target

遵循约定虽然损失一定的灵活性,无法随意安排目录结构,但能减少配置,帮助用户遵守标准。

Maven可自定义源码目录

<build>
    <sourceDirectory>...</sourceDirectory>    <!-- 不推荐自定义源码目录,提高交流成本 -->
</build>

关于超级POM
任何一个Maven项目都隐式继承了超级POM。该文件位于lib/maven-model-builder-x.x.x.jar中的org/apache/maven/model/pom-4.0.0.xml。
首先,超级POM定义了中央仓库和插件仓库,两者的地址都为中央仓库,并且都关闭了SNAPSHOT的支持。
其次,定义了项目结构

    <!-- 主输出目录 -->
    <directory>${project.basedir}/target</directory>
    <!-- 主代码输出目录 -->
    <outputDirectory>${project.build.directory}/classes</outputDirectory>
    <!-- 最终构件的名称格式 -->
    <finalName>${project.artifactId}-${project.version}</finalName>
    <!-- 测试代码输出目录 -->
    <testOutputDirectory>${project.build.directory}/test-classes</testOutputDirectory>
    <!-- 主源码目录 -->
    <sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
    <!-- 脚本源码目录 -->
    <scriptSourceDirectory>${project.basedir}/src/main/scripts</scriptSourceDirectory>
    <!-- 测试源码目录  -->
    <testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>
    <!-- 主资源目录 -->
    <resources>
      <resource>
        <directory>${project.basedir}/src/main/resources</directory>
      </resource>
    </resources>
    <!-- 测试资源目录 -->
    <testResources>
      <testResource>
        <directory>${project.basedir}/src/test/resources</directory>
      </testResource>
    </testResources>

最后,超级POM通过插件管理为核心插件设定了版本,防止由于插件版本的变化而造成构建不稳定。

反应堆

在一个多模块的Maven项目中,反应堆(Reactor)是指所有模块组成的一个构建结构。
对于单模块的项目,反应堆就是其本身,对于多模块而言,反应堆包含了各模块之间继承与依赖的关系,从而能够自动计算出合理的模块构建顺序。

1)反应堆的构建顺序

Maven按序读取POM,如果该POM没有依赖模块,则构建该模块,否则就先构建其依赖模块,如果该依赖还依赖于其他依赖,则进一步构建依赖的依赖。
模块间的依赖关系会将反应堆构成一个有向非循环图(Directed Acyclic Graph, DAG),各个模块是该图的节点,依赖关系构成了有向边。此图不允许循环,因此,当出现A依赖B,而B又依赖A时,Maven就会报错。

2)裁剪反应堆

一般来说,用户会选择构建整个项目或选择构建单个模块。
但有时,用户会想要构建完整反应堆中的部分模块,即裁剪反应堆。
Mave提供很多命令行选择支持裁剪反应堆。输入mvn -h 可以看到这些选项

-am--also-make同时构建所列模块的依赖模块
-amd-alse-make-dependents同时构建依赖于所列模块的模块
-pl--projects <arg>构建指定模块,模块间用逗号分隔
-rf-resume-from <arg>从指定的模块回复反应堆,在完整的反应堆顺序基础上指定从哪个模块开始构建

在开发过程中,灵活应用上述4个参数,可以帮助我们跳过无须构建的模块,加速构建。当项目庞大、模块特别多时,这种效果就会异常明显。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值