Maven聚合与前缀

Posted shi_zi_183

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Maven聚合与前缀相关的知识,希望对你有一定的参考价值。

Maven聚合与前缀

聚合

我们想要一次构建两个项目,而不是到两个模块的目录分别执行mvn命令,Maven聚合(或者称为多模块)这一特性就是为该需求服务的。
为了能使用一条命令就就能构件两个模块,我们需要创建一个额外的名为account-aggregator的模块,然后通过该模块构建整个项目的所有模块。这个项目本身作为一个Maven项目,它必须要有自己的POM,不过,同时作为一个聚合项目,其POM又有特殊的地方。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>account-aggregator</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Account Aggregator</name>
    <packaging>pom</packaging>
    <modules>
        <module>microservice-simple-provider-user</module>
        <module>microsevice-simple-consumer-movie</module>
    </modules>
</project>

这里第一个特殊的地方为packaging,其值为POM。对于聚合模块来说,其打包方式packaging的值必须为pom,否则就无法构建。
modules,这是实现聚合的最核心配置。用户可以通过在一个打包方式为pom的Maven项目中声明任意数量的module元素来实现模块的聚合。这里每个module的值都是一个当前POM的相对目录。
一般来说,为例方便快速定位内容,模块所处的目录名称应当与其artifactId一致,不过这不是Maven的要求,用户也可以将项目放到其他名字目录下。这是聚合的配置就需要相应地修改
为了方便用户构建项目,通常将聚合模块放在项目目录地最顶层,其他模块则作为聚合模块的子目录存在,这样当用户得到源码的时候,第一眼发现的就是聚合模块的POM,不用从多个模块中去寻找聚合模块来构建整个项目。
注:聚合模块与其他模块的目录结构并非一定要是父子关系。如果使用平行目录结构,聚合模块的POM也需要做相应的修改,以指向正确的模块目录:

<modules>
	<module>../account-email</module>
	<module>../account-persist</module>
</modules>
mvn clean install


Maven会首先解析聚合模块的POM、分析要构建的模块、并计算出一个反应堆构建顺序,然后根据这个顺序依次构建各个模块。
上述输出中显示的是各个模块的名称,而不是artifactId,这也解释了为什么要在POM中配置合理的name字段,其目的是让Maven的构建输出更清晰。

继承

前面这两个POM有着很多相同的配置,例如它们有相同的groupId和version,有很多相同的依赖。
可以使用POM继承,让我们抽取出重复的配置。

account-parent

我们需要创建POM的父子结构,然后在父POM中声明一些配置供子POM继承,以实现“一处声明,多处使用”的目的。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>account-parent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Account Parent</name>
    <packaging>pom</packaging>
</project>

该POM十分简单,它使用了与其他模块一致的groupId和version,使用的artifactId为account-parent表示这是一个父模块。需要特别注意的是,它的packaging为pom,这一点与聚合模块一样,作为父模块的POM,其打包类型也必须为pom。
由于父模块只是为了帮助消除配置的重复,因此它本身不包含除POM之外的项目文件,也就不需要src/main/java之类的文件夹了。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>com.example</groupId>
		<artifactId>account-parent</artifactId>
		<version>0.0.1-SNAPSHOT</version>
		<relativePath>../account-parent/pom.xml</relativePath>
	</parent>
	<artifactId>account-email</artifactId>
	<name>Account Email</name>
	...
</project>

上述POM中使用parent元素声明父模块,parent下的子元素groupId、artifactId和version指定了父模块的坐标,这三个元素是必须的。元素relativePath表示父模块POM的相对路径。当项目构建时Maven会首先根据relativePath检查父POM,如果找不到,再从本地仓库查找。relativePath的默认值是…/pom.xml,也就是说,Maven默认父POM在上一层目录下。
这个更新过的POM没有为account-email声明groupId和version,不过这并不代表account-email没有groupId和version、实际上,这个子模块隐式地从父模块几层楼这两个元素,这也就消除了一些不必要的配置。如果遇到子模块需要使用和父模块不一样的groupId或者version的情况,那么用户完全可以在子模块中显式声明。对于artifactId元素来说,子模块应该显式声明,一方面,如果完全继承,造成坐标冲突;另一方面,即使使用不同groupId或version,同样的artifactId容易造成混淆。

可继承的POM元素

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

依赖管理

可继承元素列表包含了dependencies元素,说明依赖是会被继承的,这是我们就会很容易想到这一特性应用到account-parent中。子模块account-email和account-persist同时依赖了org.springfamework:spring-core:2.5.6、org.springfamework:spring-beans:2.5.6、org.springfamework:spring-context:2.5.6和junit:junit:4.7,因此可以将这些依赖配置到父模块account-parent中,两个子模块就能移除这些依赖,简化配置。
上述做法是可行的,但却存在问题。到目前为止,我们能够确定这两个子模块都包含那四个依赖,不过我们无法确定将来添加的子模块就一定需要这四个依赖。假设将来项目中需要加入一个account-util模块,该模块只是提供一些简单的帮助工具,与springframework完全无关,难道也让它依赖spring-core、spring-beans和spring-context?显然是不合理的。
Maven提供的dependencyManagement元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性。在dependencyManagement元素下的依赖声明不会引入实际的依赖,不过它能够约束dependencies下的依赖使用。例如,可以在account-parent中加入这样的dependencyManagement配置。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>account-parent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Account Parent</name>
    <packaging>pom</packaging>
    <properties>
        <springframework.version>2.5.6</springframework.version>
        <junit.version>4.7</junit.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>$springframework.version</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
                <version>$springframework.version</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>$springframework.version</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context-support</artifactId>
                <version>$springframework.version</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>$junit.version</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

这里使用dependencyManagement声明的依赖既不会给account-parent引入依赖,也不会给它的子模块引入依赖,不过这段配置是会被继承的。现在修改account-email的POM

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>account-parent</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <relativePath>../account-parent/pom.xml</relativePath>
    </parent>
    <artifactId>account-email</artifactId>
    <name>Account Email</name>
    <properties>
        <javax.mail.version>1.4.1</javax.mail.version>
        <greenmail.version>1.3.lb</greenmail.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.mail</groupId>
            <artifactId>mail</artifactId>
            <version>$javax.mail.version</version>
        </dependency>
        <dependency>
            <groupId>com.icegreen</groupId>
            <artifactId>greenmail</artifactId>
            <version>$greenmail.version</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

上述POM中的依赖配置较原来简单了一些,所有的springframework依赖只配置了groupId和artifactId,省去了version,而junit依赖不仅省去了version,还省去了依赖范围scope。这些信息可以省略是因为account-email继承了account-parent中的dependencyManagement配置,完整的依赖声明已经包含在父POM中,子模块只需要配置简单的groupId和artifactId就能获得对应的依赖信息,从而引入正确的依赖。
使用这种依赖管理机制似乎不能减少大多的POM配置,不过还是强烈推荐采用这种方法。其主要原因在于在父POM中使用dependencyManagement声明依赖能够统一项目范围中依赖的版本,当依赖版本在父POM中声明之后,子模块在使用依赖的时候就无须声明版本,也就不会发生多个子模块使用依赖版本不一致的情况。这可以帮助降低依赖冲突的几率。
如果子模块不声明依赖的使用,即使该依赖已经在父POM的dependencyManagement中声明了,也不会产生任何实际的效果。

之前依赖范围的时候提到了名为import的依赖范围,推迟到现在介绍是因为该范围的依赖只在dependencyManagement元素下才有效,使用该范围的依赖通常执行一个POM,作用是将目标POM中的dependencyManagement配置导入并合并到当前POM的dependencyManagement元素中。例如相应在另一个模块中使用与下面代码清单完全一样的dependencyManagement配置,除了复制配置或者继承这两种方式之外,还可以使用import范围依赖将这一配置导入

    <dependencyManagement>
        <dependencies>
            <dependency>
				<groupId>com.example</groupId>
				<artifactId>account-parent</artifactId>
				<version>0.0.1-SNAPSHOT</version>
				<scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

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

插件管理

Maven提供了dependencyManagement元素帮助管理依赖,类似地,Maven也提供了pluginManagement元素帮助管理插件。在该元素中配置的依赖不会造成实际的插件调用行为,当POM中配置了真正的plugin元素,并且其groupId和artifactId与pluginManagement中配置的插件匹配时,pluginManagement的配置才会影响实际的插件行为。
可以结合依赖管理理解,这里不做赘述

聚合与继承的关系

多模块Maven项目中的聚合与继承其实是两个概念,其目的完全是不同的。前者主要是为了方便快速构建项目,后者主要是为了消除重复配置。
对于聚合模块来说,它知道有哪些被聚合的模块,但那些被聚合的模块不知道这个聚合模块的存在。
对于继承关系的父POM来说,它不知道有哪些子模块继承于它,但那些子模块都必须知道自己的父POM是什么。
如果非要说这两个特性的共同点,那么可以看到,聚合POM与继承关系中的父POM的packaging都必须是pom,同时,聚合模块与继承关系中的父模块除了POM之外都没有实际的内容。
在现有的实际项目中,读者往往会发现一个POM既是聚合POM,又是父POM,这么做主要是为了方便。一般来说,融合使用聚合与继承也没有什么问题。

反应堆

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

反应堆的构建顺序

构建account-aggregator会看到如下的输出

实际的构建顺序是这样形成的:Maven按序读取POM,如果该POM没有依赖模块,那么就构建该模块,否则就先构建其依赖模块,如果该依赖还依赖于其他模块,则进一步先构建依赖的依赖。
模块间的依赖关系会将反应堆构成一个有向非循环图,各个模块是该图的节点,依赖关系构成了有向边。这个图不允许出现循环,因此,当出现模块A依赖于模块B,而B有依赖于A的情况时,Maven就会报错。

裁剪反应堆

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

 -am,--also-make                        If project list is specified, also
                                        build projects required by the
                                        list
 -amd,--also-make-dependents            If project list is specified, also
                                        build projects that depend on
                                        projects on the list
 -pl,--projects <arg>                   Comma-delimited list of specified
                                        reactor projects to build instead
                                        of all projects. A project can be
                                        specified by [groupId]:artifactId
                                        or by its relative path.
 -rf,--resume-from <arg>                Resume reactor from specified
                                        project

选项描述
-am,–also-make同时构建所列模块地依赖模块
-amd,–also-make-dependents同时构建依赖于所列模块的模块
-pl,–projects <arg>构建指定的模块,模块间用逗号分隔
-rf,–resume-from<arg>从指定的模块回复反应堆
mvn clean install -pl account-email,account-persist

mvn clean install -pl account-email -am

mvn clean install -pl account-parent -amd

mvn clean install -rf account-email


使用-rf选项可以在完整的反应堆构建顺序基础上指定从那个模块开始构建。
完整的反应堆构建顺序中,account-email位于第二,因此得到上述反应堆。

mvn clean install -pl account-parent -amd -rf account-email

以上是关于Maven聚合与前缀的主要内容,如果未能解决你的问题,请参考以下文章

maven聚合与继承笔记

maven聚合与继承

maven-聚合与继承

maven 聚合与继承

Maven实战读书笔记:聚合与继承

maven之详解继承与聚合