Maven POM 文件中的继承和聚合(Inheritance & Aggregation)
之前在 IDEA 里建了一个项目专门用来学习,每个知识点都建立一个相应的模块,方便学习和查看。今天在建第二个模块的时候发现,这个模块很多的 Maven 依赖和第一个模块相同,按照我之前一个项目仅一个模块的做法,那肯定是要把依赖再复制粘贴一遍。但是在同一个项目里面,随着模块不断增加,不能每次都复制粘贴吧,而且模块都是 Maven 工程,可以利用 Maven 来对这些模块进行管理。带着这种想法,我去官网学习了下 POM 文件中的继承和聚合。
POM
POM (Project Object Model),在我们的 Maven 项目中表现出来的就是 pom.xml 文件,是对整个 Maven 项目的组织管理文件。
Super POM
我们创建 Maven 项目的时候,或是手动创建或是自动创建,都会有一个固定的文件目录结构,那为什么必须是按照这个结构来呢?添加依赖后,项目自动会从 Maven 中央仓库下载相关的 jar 包,它是在什么地方定义这个仓库地址的?这些问题的答案就是 Super POM。我们在项目中创建的 POM 文件都是继承自这个 Super POM,它是 Maven 项目的默认 POM。下面是 Maven 2.1.x 版本的 Super POM,能看到其中定义了很多默认的配置。
<project>
<modelVersion>4.0.0</modelVersion>
<name>Maven Default Project</name>
<repositories>
<repository>
<id>central</id>
<name>Maven Repository Switchboard</name>
<layout>default</layout>
<url>http://repo1.maven.org/maven2</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<name>Maven Plugin Repository</name>
<url>http://repo1.maven.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>
<!-- TODO: MNG-3731 maven-plugin-tools-api < 2.4.4 expect this to be relative... -->
<scriptSourceDirectory>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-2</version>
</plugin>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>2.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.0.2</version>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.0</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.4</version>
</plugin>
<plugin>
<artifactId>maven-ear-plugin</artifactId>
<version>2.3.1</version>
</plugin>
<plugin>
<artifactId>maven-ejb-plugin</artifactId>
<version>2.1</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.2</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>2.2</version>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.5</version>
</plugin>
<plugin>
<artifactId>maven-plugin-plugin</artifactId>
<version>2.4.3</version>
</plugin>
<plugin>
<artifactId>maven-rar-plugin</artifactId>
<version>2.2</version>
</plugin>
<plugin>
<artifactId>maven-release-plugin</artifactId>
<version>2.0-beta-8</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.3</version>
</plugin>
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>2.0-beta-7</version>
</plugin>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>2.0.4</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.4.3</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.1-alpha-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>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<inherited>true</inherited>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<inherited>true</inherited>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<updateReleaseInfo>true</updateReleaseInfo>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
Minimal POM
既然 Super POM 中已经有很多默认的 Maven 配置,那么对于我们自己创建的 POM 文件,如果不需要添加额外的功能,只需要定义最小配置即可。参考如下
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1</version>
</project>
即项目的基本信息,其中<modelVersion>
应该被设为4.0.0
,<groupId>
、<artifactId>
、<version>
提供了项目组织id、项目id以及项目版本号。
项目继承
Super POM 就是一个典型的 POM 继承的例子。我们也可以自己定义模块之间的继承方式,从父模块的 POM 配置中继承配置信息,子模块就不再需要进行重复的配置。
这里有两个模块(或者称为构件:artifact),my-app 和 my-module 模块。其中 my-module 模块继承自 my-app 模块,即 my-app 模块是 my-module 模块的父模块。根据两个模块之间不同的组织结构,有两种不同的配置方法。
对于 IDEA 来说,可以建立一个 Maven 工程,再在其中添加 Maven 模块,配置模块继承工程的 POM ,这样的话项目目录结构就是 Example 1。也可以建立一个空的工程,在其中添加多个 Maven 模块,配置模块之间的 POM 关系实现继承,这样项目目录结构就是 Example 2。对于 Eclipse 来说,就只能建立 Example 2 的目录结构了。
- 第一种结构 Example 1:
my-app
|-- my-module
| `-- pom.xml
`-- pom.xml
my-module 模块是 my-app 模块的一部分,处于 my-app 模块的根目录下,与 my-app 的 pom.xml 文件平级。这里可以通过在子模块 my-module 里的 POM 中添加一下内容实现继承。
<project>
<parent>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-module</artifactId>
<version>1</version>
</project>
这里在<project>
中指定了<parent>
元素,其中声明了父模块的组织信息。如果希望子模块继承父模块的<groupId>
和<version>
,删掉子模块中的<groupId>
和<version>
即可。
- 第二种结构 Example 2:
.
|-- my-module
| `-- pom.xml
`-- my-app
`-- pom.xml
这种结构中,my-module 和 my-app 模块在相同的目录下,两者之间处于平级关系。Eclipse 中各个项目之间就是这种目录层级关系。这种情况下我们可以添加一个<relativePath>
元素到子模块的<parent>
中,来实现继承关系,而其值就是 POM 文件的相对路径。
<project>
<parent>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1</version>
<relativePath>../my-app/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>my-module</artifactId>
</project>
这里 my-module 继承了 my-app 的 <groupId>
和<version>
,这样也就实现了继承。
POM 中多模块项目的聚合和继承针对的都是以上两种目录结构的项目。
项目聚合
聚合与继承相似,不同的是继承方式配置的是子模块的 POM,而聚合方式配置的是父模块的 POM(即 pom.xml 文件)。操作步骤如下
1. 将父模块 POMs 的 packaging 值设为 "pom"
2. 在父模块 POM 中指定子模块的路径
- 第一种结构 Example 1:
my-app
|-- my-module
| `-- pom.xml
`-- pom.xml
针对这种结构,我们在 my-app 模块(父模块)的 POM 文件中添加<modules>
元素进行配置
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1</version>
<packaging>pom</packaging>
<modules>
<module>my-module</module>
</modules>
</project>
注意,在聚合方式中,父模块的打包方式<packaging>
发生了改变,这里是pom
而不再是war
。<module>
元素中使用的是子模块相对于父模块的路径。
通过这种配置方式,在 my-app 上执行的 Maven 命令也会在 my-module 上运行。
- 第二种结构 Example 2:
.
|-- my-module
| `-- pom.xml
`-- my-app
`-- pom.xml
参考第一种结构,我们使用了<module>
元素中模块之间的相对路径,那么对于第二种结构,我们修改下相对路径即可。
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1</version>
<packaging>pom</packaging>
<modules>
<module>../my-module</module>
</modules>
</project>
项目继承和聚合的组合使用
首先说一下继承和聚合使用的场景:
继承:有多个 Maven 项目,它们具有相似的 POM 配置信息。我们可以重构所有项目,将相似的配置信息抽取出来,放到新创建的父项目里,然后让所有 Maven 项目继承父项目,这样就是继承的用法。
聚合:有一组项目,它们同时编译或执行,那我们可以创建一个父项目,将这组项目声明为父项目的多个子模块,这样的话,我们就只用编译或运行父项目即可。这是聚合的使用场景。
我们在实际开发过程中,会组合使用继承和聚合。比如说,我们需要开发一个项目,这个项目由多个模块组成,这些模块用到的 POM 配置信息相似,而且模块之间会有依赖关系,某个模块依赖于另外一个模块才能正常运行。这样情况我们就可以组合使用继承和聚合,能大大的减少项目的配置和管理成本。
使用继承和聚合,需要遵循3个规定:
1. 在每个子 POM 中指定它们的父 POM
2. 将父 POMs 打包方式设为 "pom"
3. 在父 POM 中指定子模块的路径
我们使用第二种结构作为示例。第一种结构与其相似,去掉 <relativePath>
即可。
my-app 项目中:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1</version>
<packaging>pom</packaging>
<modules>
<module>../my-module1</module>
<module>../my-module2</module>
<module>../my-module3</module>
</modules>
</project>
my-module1 项目中:
<project>
<parent>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1</version>
<relativePath>../my-app/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>my-module1</artifactId>
</project>
my-module2、my-module3 项目配置与 my-module1 相似,不列出。
实际开发过程中,多模块的管理方式可以是继承和聚合的组合使用。我在之前建立的用于学习的项目,可以仅仅使用继承。其中为了将 POM 配置信息独立出来,可以新建一个模块,这个模块只有 POM 配置信息,其他的模块依赖这个模块,即可实现 Maven 依赖的复用。
项目中的变量
Maven 允许在 POM 中使用自定义的和预设的变量。自定义变量的方式:
<properties>
<spring.version>4.3.6.RELEASE</spring.version>
</properties>
使用方式:
<version>${spring.version}</version>
实战:多模块项目 POM 示例
项目模块组织关系:
项目有一个核心模块,提供了项目其他模块运行时的各种组件。依赖于这个核心模块,项目提供了 浏览器 和 App 两种基础服务后台。在 浏览器 后台上,构建一个 Demo 项目,用于展示项目开发成果。
使用 Maven 管理:
首先构建一个父模块,所有的模块都继承这个父模块
parent-module:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>big-project</groupId>
<artifactId>parent-module</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<p.version>1.0-SNAPSHOT</p.version>
</properties>
...
<modules>
<module>../core-module</module>
<module>../browser-module</module>
<module>../app-module</module>
<module>../demo-module</module>
</modules>
</project>
建好后,和其他模块的目录结构关系如下:
.
|-- parent-module
`-- pom.xml
|-- core-module
`-- pom.xml
|-- app-module
`-- pom.xml
|-- browser-module
`-- pom.xml
|-- demo-module
`-- pom.xml
其他模块 POM 配置如下
core-module:
<project>
<modelVersion>4.0.0</modelVersion>
<artifactId>core-module</artifactId>
<parent>
<groupId>big-project</groupId>
<artifactId>parent-module</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../parent-module</relativePath>
</parent>
...
</project>
app-module:
<project>
<artifactId>app-module</artifactId>
<parent>
<groupId>big-project</groupId>
<artifactId>parent-module</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../parent-module</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>big-project</groupId>
<artifactId>core-module</artifactId>
<version>${p.version}</version>
</dependency>
...
</dependencies>
</project>
browser-module:
<project>
<artifactId>browser-module</artifactId>
<parent>
<groupId>big-project</groupId>
<artifactId>parent-module</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../parent-module</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>big-project</groupId>
<artifactId>core-module</artifactId>
<version>${p.version}</version>
</dependency>
...
</dependencies>
</project>
demo-module:
<project>
<artifactId>demo-module</artifactId>
<parent>
<groupId>big-project</groupId>
<artifactId>parent-module</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../parent-module</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>big-project</groupId>
<artifactId>browser-module</artifactId>
<version>${p.version}</version>
</dependency>
...
</dependencies>
</project>
在 parent-module 中,配置了多个 <module>
元素,其他的模块也相应地配置了 <parent>
信息,这样在编译或执行 parent-module 时,其他模块也都进行了编译或运行。
app-module 和 browser-module 均依赖于 core-module,均使用了 <dependency>
来完成这个依赖。
demo-module 依赖于 browser-module,使用<dependency>
来完成这个依赖。
参考资料:
官网链接:https://maven.apache.org/guides/introduction/introduction-to-the-pom.html
慕课网实战:https://coding.imooc.com/learn/list/134.html