Maven 聚合和继承
1. 聚合
2. 继承
<parent>
<groupId>org.apache.karaf.demos</groupId>
<artifactId>demos</artifactId>
<version>4.1.5</version>
<relativePath>../pom.xml</relativePath>
</parent>
正确设置 relativepath 非常重要。考虑这样一个情况,开发团队的新成员从源码库签出一个包含父子模块关系的 Maven 项目。由于只关心其中的某一个子模块,它就直接到该模块的目录下执行构建,这个时候,父模块是没有被安装到本地仓库的,因此如果子模块没有设置正确的的 relativepath, Maven 将无法找到父 POM,这将直接导致构建失败。如果 Maven 能够根据 relativepath 找到父 POM,它就不需要再去检査本地仓库。
2.1 可继承的 POM 元素
groupId 和 version 是可以被继承的,那么还有哪些 POM 元素可以被继承呢?以下是一个完整的列表,并附带了简单的说明:
groupId
项目组ID,项目坐标的核心元素version
项目版本,项目坐标的核心元素description
项目的描述信息organization
项目的组织信息。inceptionYear
项目的创始年份。url
项目的URL地址。developers
项目的开发者信息。contributors
项目的贡献者信息。distributionManagement
项目的部署配置。issueManagement
项目的缺陷跟踪系统信息ciManagement
项目的持续集成系统信息。scm
项目的版本控制系统信息。mailinglists
项目的邮件列表信息properties
自定义的 Maven 属性。dependencies
项目的依赖配置。dependencyManagement
项目的依赖管理配置。repositories
项目的仓库配置。build
包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等。reporting
包括项目的报告输出目录配置、报告插件配置等。
2.2 依赖管理
<dependencyManagement>
</dependencyManagement>
2.3 插件管理
<build>
<pluginManagement>
</pluginManagement>
</build>
2.4 聚合与继承的关系
对于聚合模块来说,它知道有哪些被聚合的模块,但那些被聚合的模块不知道这个聚合模块的存在。
对于继承关系的父 POM 来说,它不知道有哪些子模块继承于它,但那些子模块都必须知道自己的父 POM 是什么。
如果非要说这两个特性的共同点,那么可以看到,聚合 POM 与继承关系中的父 POM 的 packaging 都必须是 pmn,同时,聚合模块与继承关系中的父模块除了 POM 之外都没有实际的内容,如图1 所示
图6.1 聚合关系与继承关系的比较
3. 约定优于配置
Maven 提倡“约定优于配置”(Convention Over Configuration),这是 Maven 最核心的设计理念之一。
那么为什么要使用约定而不是自己更灵活的配置呢?原因之一是,使用约定可以大量减少配置。Maven 只需要一个最简单的 POM 就可以搞定。
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.karaf.demos</groupId>
<artifactId>org.apache.karaf.demos.branding.shell</artifactId>
<version>4.1.5</version>
</project>
这段配置简单得令人惊奇,但为了获得这样简洁的配置,用户是需要付出一定的代价的,那就是遵循 Maven 的的约定。Maven 会假设用户的项目是这样的:
- 源码目录为 src/main/java/
- 编译输出日录为 targel/classes
- 打包方式为 jar 包
- 输出目录为 target/
遵循约定虽然损失了一定的灵活性,用户不能随意安排目录结构,但是却能减少配置。更重要的是,遵循约定能够帮助用户遵守构建标准。
也许这时候有读者会问,如果我不想遵守约定该怎么办?这时,请首先问自己三遍,你真的需要这么做吗?如果仅仅是因为喜好,就不要要个性,个性往往意味着辆牲通用性,意味着增加无谓的复杂度:例如, Maven 允许你自定义源码目录:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.karaf.demos</groupId>
<artifactId>org.apache.karaf.demos.branding.shell</artifactId>
<version>4.1.5</version>
<build>
<sourceDirectory>src/java</sourceDirectory>
</build>
</project>
该例中源码目录就成了 src/java 而不是默认的 src/main/java。但这往往会造成交流问题,习惯 Maven 的人会奇怪,源代码去哪里了?当这种自定义大量存在的时候,交流成本就会大大提高。只有在一些特殊的情况下,这种自定义配置的方式才应该被正确使用以解决实际问题。例如你在处理遗留代码,并且没有办法更改原来的目录结构,这个时候就只能让 Maven 妥协。
本书曾多次提到超级 POM,任何一个 Maven 项目都隐式地继承自该 POM,这有点类似于任何一个 Java 类都隐式地继承于 Object 类。서此,大量超级 POM 的配置都会被所有 Maven 项目继承,这些配置也就成为了 Maven 所提倡的约定。对于 Maven3 超级 POM 在在文件 $MAVEN_HOME/lib/maven-model-builder-3.5.0.jar 中的 org/apache/maven/model/pom-4.0.0.xml 路径下。
<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>
首先超级 POM 定义了仓库及插件仓库,两者的地址都为中央仓库 htp://repo. mavel.org/ maven2,并且都关闭了 SNAPSHOT 的支持。这也就解释了为什么 Maven 默认就可以按需要从中央仓库下载构件。
<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>
</build>
这里依次定义了项目的主输出目录、主代码输出目录、最终构件的名称格式、测试代码输出目录、主源码目录、脚本源码目录、测试源码目录、主资源目录和测试资源目录。这就是 Maven 项目结构的约定。紧接着超级 POM 为核心插件设定版本。
<pluginManagement>
<!-- NOTE: These plugins will be removed from future versions of the super POM -->
<!-- They are kept for the moment as they are very unlikely to conflict with lifecycle mappings (MNG-4453) -->
<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>
可以看到,超级 POM 实际上很简单,但从这个 POM 我们就能够知晓 Maven 约定的由来,不仅理解了什么是约定,为什么要遵循约定,还能明白约定是如何实现的。
4. 反应堆
在一个多模块的 Maven 项目中,反应堆(Reactor)是指所有模块组成的一个构建结构。对于单模块的项目,反应堆就是该模块本身,但对于多模块项目来说,反应堆就包含了各模块之间继承与依赖的关系,从而能够自动计算出模块构建顺序。
4.1 反应雄的构建序
实际的构建顺序是这样形成的: Maven 按序读取 POM,如果该 POM 没有依赖模块,那么就构建该模块,否则就先构建其依赖模块,如果该依赖还依赖于其他模块,则进一步先构建依赖的依赖。
4.2 裁剪反应雄
一般来说,用户会选择构建整个项目或者选择构建单个模块,但有些时候,用户会想要仅仅构建完整反应堆中的某些个模块。换句话说,用户需要实时地裁剪反应堆。
Maven 提供很多的命令行选项支持裁剪反应堆,输入 mvn -h 可以看到这些选项:
-
-am --also-make 同时构建所列模块的依赖模块口
-
-amd --also-make-dependents 同时构建依赖于所列模块的模块
-
-pl --projects
构建指定的模块,模块间用逗号分隔口 -
-rf --resume-from
从指定的模块回复反应堆 使用-pl选项指定构建某几个模块
mvn clean install -pl account-email account-persist
使用 -amd 选项可以同时构建依赖于所列模块的模块
mvn clean install -pl account-parent -amd
使用-rf选项可以在完整的反应堆构建顺序基础上指定从哪个模块开始构建
mvn clean install - rf account-email