Maven 聚合与继承

Maven 聚合与继承

1. 背景

在这个技术发展飞速的时代,各类用户对软件的要求越来越高,软件本身也变得越

来越复杂。因此,软件设计人员往往会采用各种方式对软件划分模块,以得到更清晰的

设计及更高的重用性。

Maven 的聚合特性能够把项目的各个模块聚合在一起构建,而 Maven 的继承特性

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

模块配置的一致性。

2. 聚合

到目前为止,我们都是一个项目一个项目的构建。一个简单的需求就出来了,我们

会想要一此构建两个项目,而不是到两个模块分别执行 mvn 命令,Maven 聚合(或者

称为多模块)这一特性就是为该需求服务的。

a) 创建聚合模块

    需要创建一个新的模块,来聚合构建项目中其他所有模块。

<modelVersion>4.0.0</modelVersion>

<groupId>org.lichee</groupId>
<artifactId>lichee</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>

<name>lichee :: project</name>
<description>This is a project demo of lynch</description>
<inceptionYear>2013-2014</inceptionYear>

<modules>
  <module>parent</module>
  <module>core</module>
  <module>common</module>
  <module>support</module>
  <module>test</module>
  <module>simple-example</module>
</modules>

b) 聚合模块特征

    i.   Maven 项目

         聚合模块本身也是一个 Maven 项目。

    ii.  目录差异

        准确说除了其他模块目录和 pom.xml,没有其他任何 src、test、target 等相关目录。

 
 

    iii. pom.xml

        1) 相同的 groupId、version

        2) packaging 必须是 pom

            继承父模块也一样必须是 pom。

        3) name 提供一个相对容易阅读的名字

            配置合理的 name 字段,会让 Maven 的构建输出更清晰。

        4) modules

            实现聚合的最核心配置,用户可以通过一个打包方式为 pom 的 Maven 项目中声明,

            任意数量的 module 元素来实现模块的聚合。每个 module 的值都是一个当前 pom 的相对目录。

        5) 模块所处的目录名称与其 artifactId 一致

            文件的目录名称和 artifactId 相同,但不绝对,只是 module 里面的配置,

            必须和文件目录上的名字一样。

        6) 推荐目录格式

            通常将聚合模块放在项目目录的最顶层,其他模块则作为聚合模块的子目录存在,

            方便用户寻找聚合模块来构建整个项目。

c) 解析过程

    Maven 会首先解析聚合模块的 pom、分析要构建的模块、并计算出一个反应堆构建顺序

   (Reactor Build Order),然后根据这个顺序依次构建各个模块。


 

    i.   项目构建小结报告

    ii.  各个模块构建成功与否、花费时间

    iii. 整个构建花费的时间、使用内存等

3. 继承

很多项目中,不同的子模块有着相同的 groupId、version,相同的 spring、junit等依赖,

相同的 maven-compiler-plugin 插件等配置。这就是重复,重复意味着更多的劳动和更多的潜在问题。

在 Maven 世界中,有机制能让我们抽取出重复的配置,这就是 pom 的继承。

a) 创建继承模块

    我们需要创建 pom 的父子结构,在父 pom 中声明一些配置供子 pom 继承,一处声明,多处使用。

    父模块:

<groupId>org.lichee</groupId>
<artifactId>lichee-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>lichee :: parent</name>

    子模块:

<parent>
<groupId>org.lichee</groupId>
<artifactId>lichee-parent</artifactId>
<version>1.0.0</version>
<relativePath>../parent/</relativePath>
</parent>
<artifactId>lichee-core</artifactId>
<packaging>jar</packaging>
<name>lichee :: core</name>

b) 继承模块特征

    i.   Maven 项目

         继承模块本身也是一个 Maven 项目。

    ii.  目录差异

         准确说除了 pom.xml,没有其他任何 src、test、target 等相关目录。

 
 

    iii. pom.xml

        1) groupId、version

            虽然子模块没有声明,不过不代表子模块没有这两个属性,隐式地从父模块继承了这两个元素,

            这也就消除一些不必要的配置。如果子模块需要使用和父模块,

            不一样的 groupId 或者 version 的情况,那么用户完全可以在子模块中显式声明。

            对于 artifactId 元素来说,子模块应该显式声明。

        2) packaging 必须是 pom

            和聚合模块一样。

        3) name 提供一个相对容易阅读的名字

             配置合理的 name 字段,会让 Maven 的构建输出更清晰。

        4) relativePath

            表示父模块 pom 的相对路径,默认值为../pom.xml,也就是说,

            Maven默认父 pom 在上一层目录下。如果子模块没有设置正确的 relativePath,

            Maven 将无法找到父 pom,这将直接导致构建失败。

4. 可继承的 pom 元素

    a) groupId

        项目组 Id,项目坐标的核心元素。

    b) version

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

    c) description

        项目的描述信息。

    d) organization

        项目的组织信息。

    e) inceptionYear

        项目的创始年份。

     f) url

        项目的 url 地址。

    g) developers

        项目的开发者信息。

    h) contributors

        项目的贡献者信息。

     i) distributionManagement

        项目的部署配置。

     j) issueManagement

        项目的缺陷跟踪系统信息。

    k) ciManagement

        项目的持续集成系统信息。

     l) scm

        项目的版本控制系统信息。

   m) mailingLists

        项目的邮件信息列表。

    n) properties

        自定义的 Maven 属性。

    o) dependencies

        项目的依赖配置。

    p) dependencyManagement

        项目的依赖管理配置。

    q) repositories

        项目的仓库配置。

    r) build

        包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等。

    s) reporting

        包括项目的报告输出目录配置、报告插件配置等。

5. 依赖管理

通过 dependencies 元素,可以将父模块的依赖继承到子类中,但并不是每一个子模块

都需要相同的依赖。也不合理。

a) dependencyManagement

    让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性。

    在这个元素下的依赖声明不会引入实际的依赖,不过它能够约束 dependencies 下的依赖使用。

    父模块:

<properties>
  <junit.version>4.11</junit.version>
  <spring.version>3.2.5.RELEASE</spring.version>
  <jdk.version>1.6</jdk.version>
</properties>

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>${junit.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>${spring.version}</version>
    </dependency>
  </dependencies>
</dependencyManagement>

    子模块:

<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
  </dependency>
</dependencies>

    i.   变量提取

         spring 和 junit 依赖的版本以 maven 变量的形式提取出来,不仅消除了一些重复,

         也使得个依赖的版本处于更加明显的位置。

    ii.  引入时机

         dependencyManagement 声明的依赖不会被任何一个子模块引入,

         不过这段配置是会被子模块继承的。子模块的引入这些依赖就很简单了。

         1) 只配置 groupId 和 artifactId

             省去 version 和 scope,这些想信息可以被省略是因为子模块继承了,

             父模块的 dependencyManagement,完整的依赖声明在父 pom 中,

             子模块只需要配置简单的 groupId 和 artifactId 就能获得对应的依赖信息,

             从而引入正确的依赖。

         2) 选择性引入依赖

             如果子模块的 pom 中不声明某个依赖的使用,即使该依赖已经在父 pom

             的 dependencyManagement 中声明了,也不会产生任何实际的效果,

             也就是说不会被引入到子模块的依赖中。

        3) 最佳推荐

            这种依赖管理机制似乎不能减少太多的 pom 配置,不过还是推荐采用这种方式。

            因为在父 pom 中使用 dependencyManagement 声明依赖能够统一项目范围中的依赖版本,

            子模块在使用过程中无需声明版本,也不会发生多个子模块使用的依赖版本不一致,

            这可以降低依赖冲突的几率,对于后期升级或者修改依赖版本,也是提供了大大的便利。

        4) import 依赖范围

            这个范围的依赖只在 dependencyManagement 元素下才有效果,使用该范围的依赖通常

            指向一个 pom,作用是将目标 pom 中的dependencyManagement 配置导入并合并到,

            当前 pom 的dependencyManagement 元素中。import 范围依赖由于其特俗性,

            一般都是指向打包类型为 pom 的模块。

6. 插件管理

Maven 提供了 dependencyManagement 元素帮助管理依赖,类似的,

Maven 也提供了 pluginManagement 元素帮助管理插件。

a) pluginManagement

    该元素中配置的依赖插件不会造成实际插件的行为,当子模块 pom 中配置了真正的plugin 元素,

    并且 groupId、artifactId 与 pluginManagement 中配置的插件匹配时,

    pluginManagement 的配置才会影响实际的插件行为。

    父模块:

<pluginManagement>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.1</version>
      <configuration>
        <source>1.6</source>
        <target>1.6</target>
        <showWarnings>true</showWarnings>
      </configuration>
    </plugin>
  </plugins>
</pluginManagement>

    子模块:

<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
    </plugin>
</plugins>

    i.   变量提取

         和 dependencyManagement 一样。

    ii.  引入时机

         和 dependencyManagement 一样。

    iii. 最佳推荐

        当项目中的多个模块有同样的插件配置时,应当将配置移到父 pom 的pluginManagement 元素中。

        甚至可以要求将所有用到的插件的版本在父pom的pluginManagement元素中声明,

        子模块使用插件时不配置版本信息,这么做可以统一插件的版本,

        避免潜在的插件不一致或者不稳定问题,也便易于维护。

7. 聚合与继承的关系

a) 相同点

     聚合 pom 和继承关系中的父 pom 的 packaging 都必须是 pom,

     聚合模块与继承关系中的父模块除了 pom 之外都没有实际的内容。

     往往也会发现,一个 pom 既是聚合 pom,也是父 pom,这么做主要是为了方便,

     融合使用聚合与继承也没有什么问题。

b) 不同点

    i.   聚合

         为了方便快速构建项目。它知道有哪些被聚合的模块,但那些被聚合的模块不知道这个聚合模块。

    ii.  继承

         为了消除重复配置。它不知道有哪些子模块继承与它,但那些子模块都必须知道,

         自己的父 pom 是什么。

c) 如图所示


 

8. 约定优于配置

Maven 提倡“约定优于配置”(Convention Over Configuration),这是 Maven最核心的设计理念之一。

原因之一就是使用约定可以大量减少配置。

a) 源码目录为 src/main/java/

b) 源码资源目录为 src/main/resources/

c) 测试目录为 src/test/java/

d) 测试资源目录为 src/test/resources/

e) 编译输出目录为 target/classes/

f) 打包方式为 jar

g) 包输出目录为 target/

h) 超级 pom

    超级 pom 定义以上的目录结构、核心插件设定版本。Maven 设定核心插件的原因,

    是防止由于插件版本的变化而造成构建的不稳定。

    遵循约定虽然损失了一定的灵活性,用户不能随意安排目录结构,但是却能减少配置。

    更重要的是,遵循约定能够帮用户遵循构建标准。个性往往意味着牺牲通用性,

    意味着增加无谓的复杂度。

9. 反应堆

反应堆(Reactor)是指所有模块组成的一个构建结构。对于单模块的项目,反应堆就

是该模版本身,但对于多模块项目来说,反应堆就包含了各模块之间继承与依赖的关系。

a) 反应堆的构建顺序

    一般情况按照 modules 的声明顺序,不过也有特俗情况。Maven 按序读取 pom,

    如果该 pom 没有依赖模块,那么就构建该模块,否则就先构建其依赖模块,

    如果该依赖还依赖于其他模块,则进一步先构建依赖的依赖。

    i.   继承或者依赖

         Maven 还需要考虑模块之间的继承和依赖关系。

    ii.  有向非循环图

         模块间的依赖关系会将反应堆构成一个有向非循环图。

b) 裁剪反应堆

    用户会想要仅仅构建完整反应堆中的某些个模块。也就是用户需要实时地裁剪反应堆。

    Maven 提供很多命令行选项支持裁剪反应堆,mvn –h 查看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值