Maven 聚合与继承

  1. 安装和配置
  2. 坐标和依赖
  3. 仓库
  4. 生命周期和插件
  5. 聚合与继承
  6. 使用 Maven 进行测试
  7. 灵活的构建
  8. Archetype
  9. 附录**

聚合与继承

在这个技术飞速发展的时代,各类用户对软件的要求越来越高,软件本身也变得越来越复杂。因此软件设计人员往往会采用各种方式对软件划分模块,以得到更清晰的设计及更高的重用性。当把 Maven 应用到实际项目中的时候,也需要将项目分成不同的模块。Maven 的聚合特性能把项目的各个模块聚合一起构建,而 Maven 的继承特性则能帮助抽取各模块相同的依赖和插件等配置,在简化 POM 的同时,还能促进各个模块配置的一致性。
- 同一个项目的子模块都应该使用同样的 groupId,如果它们是一起开发和发布,还应该使用同样的 version,此外,它们的 artifaceId 还应该使用一致的前缀,以便同其它项目区分。
- 测试用例应遵守只测试接口而不测试实现这一原则,测试类一般和被测试类包名一致,测试类名 = 被测试类 + “Test”;测试方法名 = “test” + 被测试方法名[首字母大写]。

聚合

  1. 用户可以通过一个打包方式为 pom 的 Maven 项目中声明任意数量的 modules 元素来实现模块的聚合。pom.xml 中配置如下:

    <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/xsd/maven-4.0.0.xsd">    
    
        <modelVersion>4.0.0</modelVersion>  
        <groupId>groupId</groupId>  
        <artifactId>artifactId</artifactId>  
        <version>version</version>  
        <packaging>pom</packaging>
        <name>name</name>
        <!-- 待聚合模块 -->  
        <modules>  
            <module>module relative path</module>  
            ...  
        </modules>  
        ...
    </project>
    • packaging 元素值必需为 pom。
    • 聚合模块仅仅是帮助聚合其它模块构建的工具,它本身并无实质的内容,所有聚合模块仅需一个 pom.xml 文件就行了。
    • 这里 modules 的值都是一个 Maven 项目模块相对于当前 POM 文件的相对路径。
    • 为了方便的快速定位内容,模块所处的目录应当与其 artifactId 一致(不过这不是 Maven 的要求)。
    • 为了方便构建,通常将聚合模块放在项目目录层的最顶层,其它聚合模块作为子目录存在。这样当我们打开项目的时候,第一个看到的就是聚合模块的POM。当然这并不强制要求。
    • 在 POM 中配置合理的 name 字段可以让 Maven 的构建输出更清晰。
    • Maven 在构建的时候先解析聚合模块的 POM、分析要构建的模块、计算出一个反应堆构建顺序(Reactor Build Order),然后根据这个顺序依次构建各个模块。

继承

  1. 在面向对象的世界中,我们可以使用类继承在一定程度上消除重复,在 Maven 的世界中,也有类似的机制能让我们抽取出重复的配置,这是就 POM 的继承。在面向对象设计中,我们可以建立一种类的父子结构,然后在父类中声明一些字段和方法供子类继承,这样就可以做到‘一处声明,多处使用’。类似地,我们需要创建 POM 的父子结构,然后在父 POM 中声明一些配置供子 POM 继承,以实现‘一处声明,多处使用’的目的。pom.xml 配置如下:

    <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/xsd/maven-4.0.0.xsd">   
        <modelVersion>4.0.0</modelVersion>  
        <groupId>parent groupId</groupId>  
        <artifactId>parent artifaceId</artifactId>  
        <version>parent version</version>  
        <packaging>pom</packaging>  
        <name>name</name>
        ...
    </project>  
    • packaging 元素值必需为 pom。
    • 父模块只是为了帮助消除配置的重复,因此它本身不包含除 POM 外的项目文件。
    • 有了父模块,就需要让其它模块来继承它,在子模块 POM 文件中添加如下配置:

      <project>  
          <parent>
              <groupId>parent groupId</groupId>  
              <artifactId>parent artifaceId</artifactId>  
              <version>parent version</version>
              <relativePath>父 POM 文件相对路径 如:../pom.xml</relativePath>
          </parent>  
          <artifactId> sub artifactId </artifactId>  
          <name>name</name>  
        ...  
      </project> 
    • POM 中使用 parent 元素声明父模块,parent 下的 groupId、artifaceId、version 指定了父模块的坐标,这三个元素是必须的。

    • 元素 relativePath 表示父模块 POM 的相对路径,默认值为 ../pom.xml。当项目构建时,Maven 首先根据 relativePath 检查父 POM,如果找不到再从本地仓库查找。
    • 通过在子模块中显示声明元素可以覆盖继承下来的元素。

    • 一般一讲父模块和聚合模块会合并到一起。配置如下:

      <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/xsd/maven-4.0.0.xsd">   
              <modelVersion>4.0.0</modelVersion>  
              <groupId>parent groupId</groupId>  
              <artifactId>parent artifaceId</artifactId>  
              <version>parent version</version>  
              <packaging>pom</packaging>  
              <name>name</name>
              ...
              <!-- 待聚合模块 -->  
              <modules>  
                  <module>module relative path</module>  
                  ...  
              </modules>  
              ...
          </project>
  2. 可继承的 POM 元素

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

    由上面可继承元素列表可以知道 dependencies 元素会被继承,但是如果把所有子模块的依赖配置都在父模块中声明则会让子模块引入一些无用的依赖,显然是不可行的。所以 Maven 提供了 dependencyManagement 元素,既能让子模块继承父模块的依赖配置,又能保证子模块的灵活性。
    dependencyManagement 元素下的依赖声明不会引入实际的依赖,不过它可以约束 dependencies 下的依赖使用。可以在父模块的 pom.xml 中进行如下配置:

    <project>
      ...
      <modelVersion>4.0.0</modelVersion>  
      <groupId>parent groupId</groupId>  
      <artifactId>parent artifaceId</artifactId>  
      <version>parent version</version>
      <packaging>pom</packaging>  
      <name>name</name>
      <dependencyManagement>
        <dependencies>
          <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
          </dependency>
        </dependencies>
      </dependencyManagement>
      ...
    </project>

    这里使用 dependencyManagement 声明的依赖既不会给本模块引入依赖,也不会给它的子模块引入依赖,不过这段配置是会被继承的。只需要在子模块的 pom.xml 中进行如下配置:

    <project>
      ...
      <parent>
        <groupId>parent groupId</groupId>  
        <artifactId>parent artifaceId</artifactId>  
        <version>parent version</version>
        <relativePath>父 POM 文件相对路径 如:../pom.xml</relativePath>
      </parent>  
      <artifactId> sub artifactId </artifactId>  
      <name>name</name>
      <dependencies>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
        </dependency>
      </dependencies>
      ...
    </project>

    上述 POM 中的依赖配置较原来简单了一些,只需要配置依赖的 groupId 和 artifactId,省去了 version 和 scope。因为这里从父模块中继承了 dependencyManagement 配置,完整的声明包含在父 POM 中,子模块只需要配置简单的 groupId 和 artifaceId 就能获得对应的依赖信息,从而引入正确的依赖。这样似乎不能减少太多的 POM 配置,不过这样做的好处就是可以统一项目范围中依赖的版本,帮助降低依赖冲突几率。

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

    <project>
      ...
      <dependencyManagement>
        <dependencies>
          <dependency>
            <groupId>xxx-groupId</groupId>
            <artifactId>xxx-artifactId</artifactId>
            <version>xxx-version</version>
            <scope>import</scope>
            <type>pom</type>
          </dependency>
        </dependencies>
      </dependencyManagement>
      ...
    </project>

    由于 import 依赖范围的特殊性,一般都是指向打包类型为 pom 的模块。如果有多个项目,它们使用的依赖版本都是一致的,则可以定义一个使用 dependencyManagement 专门管理依赖的 POM,然后在各个项目中导入这些依赖管理配置。

  4. 插件管理

    Maven 提供了 dependencyManagement 元素帮助管理依赖,类似地,Maven 也提供了 pluginManagement 元素帮助管理插件。在该元素中配置的依赖不会造成实际插件调用行为,当 POM 中配置了真正的 plugin 元素,并且其中的 groupId 和 artifactId 与 pluginManagement 中配置的插件匹配时,pluginManagement 的配置才会影响实际的插件行为。可以在父模块 pom.xml 中进行如下配置:

    <project>
      ...
      <pluginManagement>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>2.1.1</version>
            <executions>
              <execution>
                <id>attach-sources</id>
                <phase>verify</phase>
                <goals>
                  <goal>jar-no-fork</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </pluginManagement>
      ...
    </project>

    当子模块需要使用父模块 pluginManagement 元素中声明的插件时,可以在子模块 pom.xml 中进行如下配置:

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

    如果子模块中不需要父模块 pluginManagement 配置的插件,可以尽管将其忽略。如果子模块需要不同插件配置,则可以自行配置以覆盖父模块的 pluginManagement 配置。一般都要求将所有用到的插件版本在父 POM 的 pluginManagement 元素中声明(插件的具体配置如果是统一的也应该在父模块中声明),这样可以统一项目插件版本,避免潜在的插件不一致或者不稳定问题,也更易于维护。

聚合与继承的关系

  1. 聚合与继承的不同点

    • 对于聚合模块来说,它知道有哪些被聚合的模块,但那些被聚合的模块不知道这个聚合模块的存在。
    • 对于继承关系的父 POM 来说,它不知道哪些子模块继承于它,但那些模块都必须知道自己的父 POM 是什么。
  2. 聚合与继承的相同点

    聚合与继承的相同点

    • 聚合 POM 和 继承关系中的父 POM 的 packaging 都必是 pom,同时聚合模块与继承关系中的父模块除了 POM 之外都没有实际的内容。
    • 在现在的实际项目中,往往会发现一个 POM 既是聚合 POM,又是父 POM,这么做主要是为了方便。

约定优于配置

  1. Maven 提倡“约定优于配置”(Convention Over Configuration),这是 Maven 最核心的设计理念之一。但为了获得像 Maven 现在这样简洁的配置,用户是需要付出一定代价的,那就是遵循 Maven 的约定,Maven 会假设用户的项目是这样的:

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

    遵循这样的约定虽然损失了一定的灵活性,用户不能随意安排目录结构,但是却能减少配置,更重要的是,遵循约定能够帮助用户遵守构建标准。如果你真的需要自定义这些约定,Maven 也允许你进行自定义配置,但这是非常糟糕的,pom.xml 中配置如下:

    <project>
      ...
      <build>
        <sourceDirectory>src/java</sourceDirctory>
      </build>
      ...
    </project>

    以上配置中我们自定义了源码目录 由默认的 src/main/java 修改为 src/java。但这种自定义配置大量存在往往会提高交流的成本。其实 Maven 很多默认的配置都在 超级 POM 中声明了,对于 Maven 3 超级 POM 的文件路径为: MAVENHOME/lib/mavenmodelbuilderx.x.x.jarorg/apache/maven/model/pom04.0.0.xmlMaven2POM MAVEN_HOME/lib/maven-x.x.x-uber.jar 的 org/apache/maven/model/pom0-4.0.0.xml;这里的 x.x.x 表示 Maven 的具体版本。 Maven 2 和 Maven 3 的超级 POM 内容基本一致,如下:

    <project>
      <modelVersion>4.0.0</modelVersion>
      <repositories>
        <repository>
          <id>central</id>
          <name>Central Repository</name>
          <url>https://repo.maven.apache.org/maven2</url>
          <layout>default</layout>
          <snapshots>
            <enabled>false</enabled>
          </snapshots>
        </repository>
      </repositories>
    
      <pluginRepositories>
        <pluginRepository>
          <id>central</id>
          <name>Central Repository</name>
          <url>https://repo.maven.apache.org/maven2</url>
          <layout>default</layout>
          <snapshots>
            <enabled>false</enabled>
          </snapshots>
          <releases>
            <updatePolicy>never</updatePolicy>
          </releases>
        </pluginRepository>
      </pluginRepositories>
    
      <build>
        <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>
        <pluginManagement>
          <plugins>
            <plugin>
              <artifactId>maven-antrun-plugin</artifactId>
              <version>1.3</version>
            </plugin>
            <plugin>
              <artifactId>maven-assembly-plugin</artifactId>
              <version>2.2-beta-5</version>
            </plugin>
            <plugin>
              <artifactId>maven-dependency-plugin</artifactId>
              <version>2.8</version>
            </plugin>
            <plugin>
              <artifactId>maven-release-plugin</artifactId>
              <version>2.3.2</version>
            </plugin>
          </plugins>
        </pluginManagement>
      </build>
      <reporting>
        <outputDirectory>${project.build.directory}/site</outputDirectory>
      </reporting>
      <profiles>
        <profile>
          <id>release-profile</id>
          <activation>
            <property>
              <name>performRelease</name>
              <value>true</value>
            </property>
          </activation>
          <build>
            <plugins>
              <plugin>
                <inherited>true</inherited>
                <artifactId>maven-source-plugin</artifactId>
                <executions>
                  <execution>
                    <id>attach-sources</id>
                    <goals>
                      <goal>jar</goal>
                    </goals>
                  </execution>
                </executions>
              </plugin>
              <plugin>
                <inherited>true</inherited>
                <artifactId>maven-javadoc-plugin</artifactId>
                <executions>
                  <execution>
                    <id>attach-javadocs</id>
                    <goals>
                      <goal>jar</goal>
                    </goals>
                  </execution>
                </executions>
              </plugin>
              <plugin>
                <inherited>true</inherited>
                <artifactId>maven-deploy-plugin</artifactId>
                <configuration>
                  <updateReleaseInfo>true</updateReleaseInfo>
                </configuration>
              </plugin>
            </plugins>
          </build>
        </profile>
      </profiles>
    </project>
    • 超级 POM 中定义了默认的仓库和插件仓库,并都关闭了 SNAPSHOT 的支持。
    • 超级 POM 中定义了项目的主输出目录,主代码输出目录、最终构件的名称格式、测试代码输出目录、主源码目录、脚本源码目录、测试源码目录、主资源目录和测试资源目录。
    • 超级 POM 中定义了默认的一些插件。

反应堆

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

  1. 反应堆的构建顺序

    Maven 的构建顺序: Maven 按序读取 POM,如果该 POM 没有依赖模块,那么就构建该模块,否则就先构建其依赖模块,如果该依赖模块还依赖于其它模块,则进一步先构建依赖的依赖。

  2. 裁剪反应堆

    一般来说,用户会选择构建整个项目或者选择构建单个模块,但有些时候,用户会想要仅仅构建完整反应堆中的某些个模块。换句话说,用户需要实时地裁剪反应堆。Maven 提供很多的命令行选项支持裁剪反应堆,输入 mvn -h 可以看到这些选项:

    • -am, –also-make 同时构建所列模块的依赖模块
    • -amd –also-make-dependents 同时构建依赖于所列模块的模块
    • -pl, –projects 构建指定模块,模块之间用逗号分隔
    • -rf –resume-from 从指定的模块回复反应堆

    反应堆
    聚合模块C中的聚合顺序如下:

    <modules>  
      <module>P</module>
      <module>A</module>
      <module>B</module>
      <module>D</module>
    </modules>

    以上图的 Maven 模块关系为例来解释这几个选项的作用:

    ================================================================
    
    
    # 在模块C 下执行
    
    $ mvn clean install
    [INFO] --------------------------------------------------------
    [INFO] Reactor Build Order:
    [INFO]
    [INFO] P
    [INFO] D
    [INFO] A
    [INFO] B
    [INFO] C
    [INFO] --------------------------------------------------------
    // 因为 P 是 A、B 的父模块,并不是 A、B 的依赖,所以如果P不在聚合模块中声明,以上命令将不会构建P
    // 聚合模块 C 将最后构建
    
    ================================================================
    
    # 使用 -pl 选项指定构建某几个模块
    
    $ mvn clean install -pl A,B
    [INFO] --------------------------------------------------------
    [INFO] Reactor Build Order:
    [INFO]
    [INFO] A
    [INFO] B
    [INFO] --------------------------------------------------------
    // 以上命令中的 A、B也就是聚合模块 POM 中 module 元素的值。即通过一个 Maven 项目模块相对于当前聚合模块 POM 文件的相对路径指定需要构建的模块
    
    ================================================================
    
    # 使用 -am 选项同时构建所列模块和依赖的模块
    
    $ mvn clean install -pl A -am
    [INFO] --------------------------------------------------------
    [INFO] Reactor Build Order:
    [INFO]
    [INFO] P
    [INFO] D
    [INFO] A
    [INFO] --------------------------------------------------------
    // 如果 父模块 P 没有在聚合模块声明,执行上述命令就不会构建 父模块 P
    
    ================================================================
    
    # 使用 -amd 选项同时构建依赖于所列模块的模块
    
    $ mvn clean install -pl P -amd
    [INFO] --------------------------------------------------------
    [INFO] Reactor Build Order:
    [INFO]
    [INFO] P
    [INFO] A
    [INFO] B
    [INFO] --------------------------------------------------------
    // 上面 A、B 的构建顺序按照聚合模块中的声明顺序构建
    
    ================================================================
    
    # 使用 -rf 选项可以在完整的反应堆构建顺序的基础上指定从哪个模块开始构建
    
    $ mvn clean install -rf A
    [INFO] --------------------------------------------------------
    [INFO] Reactor Build Order:
    [INFO]
    [INFO] A
    [INFO] B
    [INFO] C
    [INFO] --------------------------------------------------------

    在实际开发中,灵活应用上述4个参数,可以帮助我们路过无须构建的模块,从而提高构建速度。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值