首页 > 代码库 > Maven学习笔记(七):聚合与继承

Maven学习笔记(七):聚合与继承

     软件设计人员往往会采用各种方式对软件划分模块,以得到更清晰的设计及更高的重用性。当把Maven应用到实际项目中的时候,也需要将项目分成不同模块。Maven的聚合特性能够把项目的各个模块聚合在一起构建,而Maven的继承特性则能帮助抽取各模块相同的依赖和插件等配置,在简化POM的同时,还能促进各个模块配置的一致性。
     <maven实战>一书实现了两个模块account-email和account-persist用以实现一个用户注册的项目。 在本文中,也使用这个例子来讲述聚合与继承。

聚合: 

     由于有account-email和account-persist模块的存在,这时就有一个需求冒出来了:我们会想要一次构建两个项目,而不是到两个模块的目录下分别执行mvn命令。Maven聚合(或者称为多模块)这一特性就是为该需求服务的。
     我们需要额外创建一个名为account-aggregator的模块,然后通过该模块构建整个项目的所有模块。account-aggregator本身作为一个Maven项目,它有自己POM,不过作为一个聚合项目,其POM又有特殊的地方。如下为account-aggregator的pom.xml内容。
<?xml version="1.0" encoding="UTF-8"?>
<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.juvenxu.mvnbook.account</groupId>
        <artifactId>account-aggregator</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <packaging>pom</packaging>
        <name>Account Aggregator</name>
        <modules>
             <module>account-email</module>
             <module>account-persist</module>
        <modules>   
</project>

     需要注意的是,对于聚合模块来说,其打包方式packaging的值必须为pom,否则就无法构建。
     <modules>元素是实现聚合的最核心的配置,用户可以通过在一个打包方式为pom的Maven项目中声明任意数量的module元素来实现模块的聚合。这里每个module的值都是一个当前POM的相对目录。
     为了方便用户构建项目,通常将聚合模块放在项目目录的最顶层,其他模块则作为聚合模块的子目录存在,这样当用户得到源码的时候,第一眼发现的就是聚合模块的POM,不用从多个模块中去寻找聚合模块来构建整个项目。
     account-aggregator没有src/main/java、src/test/java等目录,因为聚合模块仅仅是帮助聚合其他模块构建的工具,它本身并无实质的内容。
     我们可以在聚合模块下运行mvn clean install命令查看聚合是怎样进行模块的构建的。
     可以看到,Maven会首先解析聚合模块POM、分析要构建的模块、并计算出一个反应堆构建顺序(Reactor Build Order),然后根据这个顺序依次构建各个模块。反应堆是所有模块组成的一个构建结构。后面会详细讲解Maven的反应堆。

继承:

     面向对象设计中,程序员可以建立一种类的父子结构,然后在父类中声明一些字段和方法供子类继承,这样就可以做到"一处声明,多处使用"。类似的,我们需要创建父子结构,然后在父POM中声明一些配置供子POM继承,以实现"一处声明,多处使用"的目的。
     父模块的POM与聚合模块的POM内容相似。作为父模块POM,其打包类型也必须为pom。
     由于父模块只是为了帮助消除配置的重复,因此它本身不包含除POM之外的项目文件,也就不需要src/main/java之类的文件夹了。
     在子模块的pom中,使用parent元素声明父模块,parent下的子元素groupId、artifactId和version指定了父模块的坐标,这三个元素是必须的。元素relativePath表示父模块POM的相对路径。当项目构建时,Maven会首先根据relativePath检查父POM,如果找不到,再从本地仓库查找。relativePath的默认值是../pom.xml,也就是说,Maven默认父POM在上一层目录下。
可继承的POM元素:
     父POM的一些元素可以被子POM所继承。以下简单说明:
  • groupId:项目组ID,项目坐标的核心元素
  • version:项目版本,项目坐标的核心因素
  • description:项目的描述信息
  • organization:项目的组织信息
  • inceptionYear:项目的创始年份
  • url:项目的URL地址
  • developers:项目的开发者信息
  • contributors:项目的贡献者信息
  • distributionManagement:项目的部署配置
  • issueManagement:项目的缺陷跟踪系统信息
  • ciManagement:项目的持续集成系统信息
  • scm:项目的版本控制系统西溪
  • malilingLists:项目的邮件列表信息
  • properties:自定义的Maven属性
  • dependencies:项目的依赖配置
  • dependencyManagement:项目的依赖管理配置
  • repositories:项目的仓库配置
  • build:包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等
  • reporting:包括项目的报告输出目录配置、报告插件配置等
依赖管理:
     Maven提供的denpencyManagement元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性。在dependencyManagement元素下的依赖声明不会引入实际的依赖,不过它能够约束dependencies下的依赖使用。
     父模块在dependencyManagement中声明依赖,子模块会继承父模块中的dependencyManagement配置,完整的依赖声明已经包含在父POM中,子POM只需要配置简单的groupId和artifactId就能获得对应的依赖信息,从而引入正确的依赖。
     使用这种依赖机制虽然不能减少太多的POM的配置,但是通过dependencyManagement声明依赖能够统一项目范围中依赖的版本,当依赖版本在父POM中声明之后,子模块在使用依赖的时候就无须声明版本,也就不会发生多个子模块使用依赖版本不一致的情况。这可以帮助降低依赖冲突的几率。
     如果子模块不声明依赖的使用,即使该依赖已经在父POM的dependencyManagement中声明了,也不会产生任何实际的效果。
import依赖范围:
     import依赖范围需要在dependencyManagement元素下使用才有效果,使用该范围的依赖通常指向一个POM,作用是将目标POM中dependencyManagement配置导入并合并到当前POM的dependencyManagement元素中。例如我们想要在一个模块中使用与另一个模块完全一样的dependencyManagement配置,除了复制配置或者继承这两者方式之外,还可以使用import范围依赖将这一配置导入。代码如下:
<dependencyManagemen>
     <dependencies>
          <denpendency>
        <groupId>com.juvenxu.mvnbook.account</groupId>
        <artifactId>account-parent</artifactId>
        <version>1.0-SNAPSHOT</version>              
        <type>pom</type>
        <scope>import</scope> 
          <denpendency>
     <dependencies>
<dependencyManagement> 
    
     import范围依赖由于其特殊性,一般都是指向打包类型为pom的模块。如果有多个项目,它们使用的依赖版本都是一致的,则就可以定义一个使用dependencyManagement专门管理依赖的POM,然后在各个项目中导入这些依赖管理配置。
插件管理:
     Maven也提供了pluginManagement元素帮助管理插件,插件管理的使用与依赖管理的使用方式类似。
     如果子模块需要不同的插件配置,则可以自行配置以覆盖父模块的pluginManagement配置。
     当项目中的多个模块有同样的插件配置时,应当将配置移到父POM的pluginManagement元素中。即使各个模块对于同一插件的具体配置不尽相同,也应当使用父POM的pluginManagement元素统一声明插件的版本。甚至可以要求将所有用到的插件的版本在父POM的pluginManagement元素中声明,子模块使用插件时不配置版本信息,这样做可以统一项目的插件版本,避免潜在的插件不一致或者不稳定的情况,也更易于维护。
聚合与继承的关系:
     在实际的项目中,我们通常把聚合模块和父模块,放在一起实现。

反应堆:

     在一个多模块的Maven项目中,反应堆(Reactor)是指所有模块组成的一个构建结构。对于单模块的项目,反应堆就是该模块本身,但对于多模块的项目来说,反应堆就包含了各模块之间继承与依赖的关系,从而能够自动计算出合理的模块构建顺序。
反应堆的构建顺序:
     实际的构建顺序是这样形成的:Maven按序读取POM,如果该POM没有依赖模块,那么就构件该模块,否则就先构建其依赖模块,如果该依赖还依赖于其他模块,则进一步先构建依赖的依赖。
     模块间的依赖关系会将反应堆构成一个有向非循环图(DAG),各个模块是该图的节点,依赖关系构成了有向边。这个图不允许出现循环,因此,当出现模块A依赖于B,而B又依赖于A的情况时,Maven就会报错。          

Maven学习笔记(七):聚合与继承