Maven实战-聚合与继承

Maven实战-聚合与继承

1、pom.xml文件build配置

<build>
    <testResources>
        <testResource>
            <directory>src/test/resourcesdirectory>
            <filtering>truefiltering>
        testResource>
    testResources>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.pluginsgroupId>
            <artifactId>maven-compiler-pluginartifactId>
            <version>3.10.1version>
            <configuration>
                <source>1.8source>
                <source>1.8source>
            configuration>
        plugin>
        <plugin>
            <groupId>org.apache.maven.pluginsgroupId>
            <artifactId>maven-resources-pluginartifactId>
            <version>3.2.0version>
            <configuration>
                <encoding>UTF-8encoding>
            configuration>
        plugin>
    plugins>
build>

build 元素下还包含了两个插件的配置。首先是配置 maven-compiler-plugin 支持Java 1.8,我们知道,虽然这里

没有配置插件版本,但由于 maven-compiler-plugin 是核心插件,它的版本已经在超级 POM 中设定了。此外,

如果这里不配置 groupld,Maven 也会使用默认的 groupld org.apache.maven.plugins。除了 maven-compiler-

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

2、聚合配置

<modules>
    <module>account-emailmodule>
    <module>account-persistmodule>
modules>

3、继承

父 pom:

<modelVersion>4.0.0modelVersion>
<groupId>com.juvenxu.mvnbook.accountgroupId>
<artifactId>account-parentartifactId>
<version>1.0.0-SNAPSHOTversion>
<packaging>pompackaging>
<name>Account Parentname>

子 pom:

<parent>
    <groupId>com.juvenxu.mvnbook.accountgroupId>
    <artifactId>account-parentartifactId>
    <version>1.0.0-SNAPSHOTversion>
    <relativePath>../account-parent/pom.xmlrelativePath>
parent>

上述 POM 中使用 parent 元素声明父模块,parent 下的子元素 groupld、artifactld 和 version 指定了父模块的

坐标,这三个元素是必须的。元素 relativePath 表示父模块 POM 的相对路径,该例中的

../account-parent/pom.xml 表示父 POM 的位置在与 account-email/ 目录平行的 account-parent/ 目录

下。

当项目构建时,Maven 会首先根据 relativePath 检查父 POM,如果找不到,再从本地仓库查找。relativePath 的

默认值是 ../pom.xml,也就是说,Maven 默认父 POM 在上一层目录下。

4、可继承的pom元素

在上面我们看到,groupld 和 version是可以被继承的,那么还有哪些 POM 元素可以被继承呢?以下是一个完整的

列表,并附带了简单的说明:

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

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

  • description:项目的描述信息。

  • organization:项目的组织信息。

  • inceptionYear:项目的创始年份。

  • url:项目的URL地址。

  • developers:项目的开发者信息。

  • contributors:项目的贡献者信息。

  • distributionManagement:项目的部署配置。

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

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

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

  • mailingLists:项目的邮件列表信息。

  • properties:自定义的 Maven 属性。

  • dependencies:项目的依赖配置。

  • dependencyManagement:项目的依赖管理配置。

  • repositories:项目的仓库配置。

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

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

5、依赖管理

可继承元素列表包含了 dependencies 元素,说明依赖是会被继承的。

Maven 提供的 dependencyManagement 元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用

的灵活性。在 dependencyManagement 元素下的依赖声明不会引人实际的依赖,不过它能够约束dependencies

下的依赖使用。例如,可以在 account-parent 中加人这样的 dependencyManagement 配置。


<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">
    <parent>
        <artifactId>ProjectMavenartifactId>
        <groupId>org.examplegroupId>
        <version>1.0-SNAPSHOTversion>
    parent>

    <modelVersion>4.0.0modelVersion>
    <artifactId>account-parentartifactId>
    <packaging>pompackaging>

    <properties>
        <maven.compiler.source>8maven.compiler.source>
        <maven.compiler.target>8maven.compiler.target>
        <elasticsearch.version>7.15.2elasticsearch.version>
        <redisson.version>3.13.6redisson.version>
        <junit.version>4.7junit.version>
    properties>

    <dependencyManagement>

        <dependencies>
            <dependency>
                <groupId>org.redissongroupId>
                <artifactId>redissonartifactId>
                <version>3.13.6version>
            dependency>
            <dependency>
                <groupId>org.elasticsearchgroupId>
                <artifactId>elasticsearchartifactId>
                <version>7.15.2version>
            dependency>
            <dependency>
                <groupId>junitgroupId>
                <artifactId>junitartifactId>
                <version>${junit.version}version>
                <scope>testscope>
            dependency>
        dependencies>
    dependencyManagement>

project>

首先该父 POM 将 springframework 和 junit 依赖的版本以 Maven 变量的形式提取了出来,不仅消除了一些重

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

这里使用 dependencyManagement 声明的依赖既不会给 account-parent 引入依赖,也不会给它的子模块引入

依赖、不过这段配置是会被继承的。现在 account-email 的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.0modelVersion>
    <artifactId>account-emailartifactId>

    <parent>
        <artifactId>account-parentartifactId>
        <groupId>org.examplegroupId>
        <version>1.0-SNAPSHOTversion>
        <relativePath>../account-parent/pom.xmlrelativePath>
    parent>

    <properties>
        <maven.compiler.source>8maven.compiler.source>
        <maven.compiler.target>8maven.compiler.target>
        <javax.mail.version>1.4.1javax.mail.version>
        <mail.version>1.4.1mail.version>
    properties>

    <dependencies>
        <dependency>
            <groupId>org.elasticsearchgroupId>
            <artifactId>elasticsearchartifactId>
        dependency>
        <dependency>
            <groupId>org.redissongroupId>
            <artifactId>redissonartifactId>
        dependency>
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
        dependency>
        <dependency>
            <groupId>javax.mailgroupId>
            <artifactId>mailartifactId>
            <version>${mail.version}version>
        dependency>
    dependencies>

project>

这里 elasticsearch 和 redisson 如果不指定版本,则使用父类指定的版本。

这里没有声明 spring-context-support,那么该依赖就不会被引入。这正是 dependency-

Management 的灵活性所在。

这里介绍名为 import 的依赖范围,推迟到现在介绍是因为该范围的依赖只在 dependencyManagement 元素下

才有效果,使用该范围的依赖通常指向一个 POM,作用是将目标POM中的 dependencyManagement 配置导入

并合并到当前 POM 的 dependencyManagement 元素中。例如想要在另外一个模块中使用与上面完全一样的

dependencyManagement 配置,除了复制配置或者继承这两种方式之外,还可以使用 import 范围依赖将这一配

置导入。

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.examplegroupId>
            <artifactId>account-parentartifactId>
            <version>1.0-SNAPSHOTversion>
            <type>pomtype>
            <scope>importscope>
        dependency>
    dependencies>
dependencyManagement>

注意,上述代码中依赖的 type 值为pom,import 范围依赖由于其特殊性,一般都是指向打包类型为pom的模

块。如果有多个项目,它们使用的依赖版本都是一致的,则就可以定义一个使用 dependencyManagement 专门

管理依赖的 POM,然后在各个项目中导入这些依赖管理配置。

6、插件管理

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

素帮助管理插件。在该元素中配置的依赖不会造成实际的插件调用行为,当 POM 中配置了真正的 plugin 元素,

并且其 groupld 和 artifactld 与 pluginManagement 中配置的插件匹配时,pluginManagement 的配置才会影响

实际的插件行为。

前面配置了 maven-source-plugin,将其 jar-no-fork 目标绑定到了 verity 生命周期阶段,以生成项目源码包。如

果一个项目中有很多子模块,并且需要得到所有这些模块的源码包,那么很显然,为所有模块重复类似的插件配置

不是最好的办法。这时更好的方法是在父 POM 中使用 pluginManagement 配置插件。

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-source-pluginartifactId>
                <version>3.2.1version>
                <executions>
                    <execution>
                        <id>attach-sourcesid>
                        <phase>verifyphase>
                        <goals>
                            <goal>jar-no-forkgoal>
                        goals>
                    execution>
                executions>
            plugin>
        plugins>
    pluginManagement>
build>

当子模块需要生成源码包的时候,只需要如下简单的配置。

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.pluginsgroupId>
            <artifactId>maven-source-pluginartifactId>
        plugin>
    plugins>
build>

子模块声明使用了 maven-source-plugin 插件,同时又继承了父模块的 pluginManagement配置,两者基于

groupld 和 artifactId 匹配合并之后进行了插件配置。

如果子模块不需要使用父模块中 pluginManagement 配置的插件,可以尽管将其忽略。

如果子模块需要不同的插件配置,则可以自行配置以覆盖父模块的 pluginManagement 配置。

有了 pluginManagement元素,account-email 的 POM 也能得以简化了,它们都配置了

maven-compiler-plugin 和 maven-resources-plugin。可以将这两个插件的配置移到 account-parent 的

pluginManagement元素中。

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-source-pluginartifactId>
                <version>3.2.1version>
                <executions>
                    <execution>
                        <id>attach-sourcesid>
                        <phase>verifyphase>
                        <goals>
                            <goal>jar-no-forkgoal>
                        goals>
                    execution>
                executions>
            plugin>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <version>3.10.1version>
                <configuration>
                    <source>1.8source>
                    <target>1.8target>
                configuration>
            plugin>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-resources-pluginartifactId>
                <version>3.2.0version>
                <configuration>
                    <encoding>UTF-8encoding>
                configuration>
            plugin>
        plugins>
    pluginManagement>
build>

account-email 可以完全地移除关于 maven-compiler-plugin 和 maven-resources-plugin 的配置,但它们仍能

享受这两个插件的服务,前一插件开启了 Java 8 编译的支持,后一插件也会使用UTF-8编码处理资源文件。这背后

涉及了很多Maven 机制,首先,内置的插件绑定关系将两个插件绑定到了 account-email 的生命周期上;其次,

超级 POM 为这两个插件声明了版本;最后,account-parent 中的 pluginManagement 对这两个插件的行为进行

了配置。

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

块对于同一插件的具体配置不尽相同,也应当使用父 POM 的 pluginManagement 元素统-声明插件的版本。甚至

可以要求将所有用到的插件的版本在父 POM 的 pluginManagement 元素中声明,子模块使用插件时不配置版本

信息,这么做可以统一项目的插件版本,避免潜在的插件不一致或者不稳定问题,也更易于维护。

7、聚合与继承的关系

基于前面的内容,读者可以了解到,多模块 Maven 项目中的聚合与继承其实是两个概念,其目的完全是不同的。

前者主要是为了方便快速构建项目,后者主要是为了消除重复配置。

对于聚合模块来说,它知道有哪些被聚合的模块,但那些被聚合的模块不知道这个聚合模块的存在。

对于继承关系的父POM来说,它不知道有哪些子模块继承于它,但那些子模块都必须知道自己的父POM是什么。

如果非要说这两个特性的共同点,那么可以看到,聚合POM与继承关系中的父 POM 的 packaging 都必须是

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

在现有的实际项目中,读者往往会发现一个 POM 既是聚合 POM,又是父 POM,这么做主要是为了方便。一般来

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

8、约定优于配置

标准的重要性已不用过多强调,想象一下,如果不是所有程序员都基于 HTTP 协议开发Web 应用,互联网会乱成

怎样。各个版本的IE、Firefox等浏览器之间的差别已经让很多开发者头痛不已。而Java 成功的重要原因之一就是

它能屏蔽大部分操作系统的差异,XML流行的原因之一是所有语言都接受它。Maven 当然还不能和这些既成功又

成熟的技术相比,但Maven 的用户都应该清楚,Maven 提倡约定优于配置(Convention Over Configuration),这

是 Maven 最核心的设计理念之一。

那么为什么要使用约定而不是自己更灵活的配置呢?原因之一是,使用约定可以大量减少配置。

Maven 会假设用户的项目是这样的:

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

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

更重要的是,遵循约定能够帮助用户遵守构建标准。

多次提到超级POM,任何一个Maven 项目都隐式地继承自该POM,这有点类似于任何一个 Java 类都隐式地继承

于 Object 类。因此,大量超级 POM 的配置都会被所 Maven 项目继承,这些配置也就成为了 Maven 所提倡的约

定。

Maven 设定核心插件的原因是防止由于插件版本的变化而造成构建不稳定。

9、反应堆

在一个多模块的Maven 项目中,反应堆(Reactor)是指所有模块组成的一个构建结构。

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

系,从而能够自动计算出合理的模块构建顺序。

9.1 反应堆的构建顺序

实际的构建顺序是这样形成的:Maven 按序读取 POM,如果该 POM 没有依赖模块,那么就构建该模块,否则就

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

模块间的依赖关系会将反应堆构成一个有向非循环图(Directed Acyelic Graph,DAG),各个模块是该图的节点,

依赖关系构成了有向边。这个图不允许出现循环,因此,当出现模块A依赖于B,而B又依赖于A的情况时,Maven

就会报错。

9.2 裁剪反应堆

一般来说,用户会选择构建整个项目或者选择构建单个模块,但有些时候,用户会想要仅仅构建完整反应堆中的某

些个模块。换句话说,用户需要实时地裁剪反应堆。

Maven 提供很多的命令行选项支持裁剪反应堆,输入 mvn -h 可以看到这些选项:

  • -am,--also-make 同时构建所列模块的依赖模块

  • -amd,--also-make-dependents 同时构建依赖于所列模块的模块

  • -pl,--projects 构建指定的模块,模块间用逗号分隔

  • -rf,-resume-from 从指定的模块回复反应堆

$ mvn clean install
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] accout-aggregator
[INFO] accout-parent
[INFO] accout-email
[INFO] accoun-persist
[INFO]
......

可以使用 -pl 选项指定构建某几个模块,如运行如下命令:

$ mvn clean install -pl accout-email,accoun-persist
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] accout-email
[INFO] accoun-persist
[INFO]
[INFO] ------------------------------------------------------------------------

使用 -am 选项可以同时构建所列模块的依赖模块。例如:

$ mvn clean install -pl accout-email -am
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] accout-aggregator
[INFO] accout-email
[INFO]
[INFO] ------------------------------------------------------------------------

使用 -amd 选项可以同时构建依赖于所列模块的模块。例如:

$ mvn clean install -pl accout-parent -amd
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building accout-parent 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------

使用 -rf 选项可以在完整的反应堆构建顺序基础上指定从哪个模块开始构建。例如:

$ mvn clean install -rf accout-email
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] accout-email
[INFO] accoun-persist
[INFO]
[INFO] ------------------------------------------------------------------------

在 -pl -am 或者 -pl -amd的基础上,还能应用 -rf 参数,以对裁剪后的反应堆再次裁剪。例如:

$ mvn clean install -pl accout-parent -amd -rf accout-email

在开发过程中,灵活应用上述4个参数。可以帮助我们跳过无须构建的模块,从而加速构建。在项目庞大、模块特

别多的时候,这种效果就会异常明显。

你可能感兴趣的:(maven,maven)