Maven 聚合与继承

发布于 2024-01-09 14:32:44 字数 13167 浏览 21 评论 0

在这个技术飞速发展的时代,软件开发的工程量也与日俱增,分模块开发的概念也逐渐成为了主流。分模块开发带来了更好的设计思想,以及更高的代码重用性。

1. 分模块开发

以一个简单的博客系统为例,在传统开发中我们将所有代码写在同一个项目中,实际上我们可以将一个 WEB 项目分为三个子模块:

一般来说,一个项目的子模块应该使用同样的 groupId,如果他们一起开发和发布,还应该使用同样的 version,此外,它们的 artifactId 还应该使用相同的前缀,以方便同其他项目区分。

blog-web 的 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>com.wennry.blog</groupId>
	<artifactId>blog-web</artifactId>
	<packaging>war</packaging>
    <name>Blog Dao</name>
	<version>1.0.0-SNAPSHOT</version>
</project>

blog-service 的 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>com.wennry.blog</groupId>
	<artifactId>blog-service</artifactId>
	<packaging>jar</packaging>
    <name>Blog Service</name>
	<version>1.0.0-SNAPSHOT</version>
</project>

blog-dao 的 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>com.wennry.blog</groupId>
	<artifactId>blog-dao</artifactId>
	<packaging>jar</packaging>
    <!--在使用 Maven 命令进行构建,输出时会以 name 属性输出信息,使得输出更加直观-->
    <name>Blog Dao</name>  
	<version>1.0.0-SNAPSHOT</version>
</project>

​此时三个项目相互独立, 如果我们此时想要一次构建三个项目该怎么办呢,而不是分别执行三次 Maven 命令。Maven 聚合就是为这一任务诞生的

2. 聚合

​为了能够实现一条命令构建三个模块的效果,我们需要创建一个额外的名为 blog-aggregator 的模块,然后通过该模块构建整个项目的所有模块。blog-aggregator 本身是一个 Maven 项目,它必须要有自己的 POM,同时作为一个聚合项目,其 POM 文件又有特殊的地方。如下为 blog-aggregator 的 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>com.wennry.blog</groupId>
	<artifactId>blog-aggregator</artifactId>
	<packaging>pom</packaging>
    <name>Blog Aggregator</name>
	<version>1.0.0-SNAPSHOT</version>
    <modules>
    	<module>blog-web</module>
        <module>blog-service</module>
        <module>blog-dao</module>
    </modules>
</project>

​对于聚合项目来说,其打包方式 packaging 的值必须为 pom,不然无法构建。用户在 POM 中通过任意数量的 module 元素来实现模块聚合。 这里每个 module 的值都是一个当前 POM 的相对目录 。例如 blog-aggregator 的 POM 路径为:D:/..../blog-aggregator/pom.xml,那么 blog-web 的目录就为:D:/..../blog-aggregator/blog-web/。blog-web 里面又是一个完整的 Maven 项目结构。离开聚合项目后依然可以独立构建。

​一般来说,为了方便定位内容,artifactId 应与当前目录名称一致,不过这不是 Maven 的要求,用户可以将 blog-web 项目放在 web-blog 目录下,这时聚合项目的配置就应该改为 <module>web-blog</module>

​为了方便用户构建项目,通常将聚合项目放在最顶层,其他模块则作为聚合项目的子目录存在。但是这仍然不是 Maven 的规定,我们仍然可以使用平行结构,但是聚合模块的 POM 也需要做出相应的修改:

<modules>
	<module>../blog-web</module>
	<module>../blog-service</module>
	<module>../blog-dao</module>
</modules>

3. 继承

​聚合解决了分模块开发需要多次构建的问题,但是分模块开发中还有一个问题需要解决,那就是 POM 重复配置的问题,例如 web、service、dao 三个模块中 groupId、version 以及各模块所依赖的 JAR 以及插件都有一定的重复。重复往往就意味着更多的劳动和更多的潜在问题。在面向对象的世界中可以使用类继承在一定程度上消除重复。在 Maven 的世界中,也有类似的继承机制让我们抽出重复的配置。

3.1 定义父模块

​我们需要创建一个父 POM 来对重复配置进行抽取,以实现一次声明多处使用的目的。我们继续以上面的项目结构为基础,在 blog-aggregator 下创建一个名为 blog-parent 的子目录,在 blog-parent 目录下创建 pom.xml。由于父模块只是为了帮助消除配置的重复,因此他本并不包含除 POM 之外的项目文件,也就不需要 src/main/java 之类的文件夹了。

<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>com.wennry.blog</groupId>
	<artifactId>blog-parent</artifactId>
	<packaging>pom</packaging>
    <name>Blog Parent</name>
	<version>1.0.0-SNAPSHOT</version>
</project>

此时整个项目的目录结构如下:

有了父模块之后我们需要让子模块继承父模块,我们就以 blog-web 模块为例:

<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>
    <parent>
    	<groupId>com.wennry.blog</groupId>
        <artifactId>blog-parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <relativePath>../blog-parent/pom.xml</relativePath>
    </parent>
	<artifactId>blog-web</artifactId>
	<packaging>war</packaging>
    <name>Blog Dao</name>
</project>

​在子模块中我们需要使用 parent 标签指定父模块,以达到继承的效果。需要注意的是,relativePath 表示父模块 POM 的相对路径。当项目构件时,Maven 会首先根据 relativePath 检查父 POM,如果找不到再去本地仓库查找。 relativePath 的默认值为../pom.xml

​正确设置 relativePath 非常重要。考虑这一情况,开发团队的新成员从源码库迁出一个包含父模块关系的 Maven 项目。由于只关心其中的某一个子模块是,它就直接到该模块的目录下进行构建,但是此时父模块并没有安装到本地仓库,因此如果子模块没有正确设置 relativePath 属性,Maven 无法找到父 POM,这将直接导致构建失败。

​在子模块中如果继承了父模块,那么通常就不要定义 groupId、version 了,它会默认继承父 POM 的两个属性。

3.2 可继承的 POM 元素

在上面我们提到了 groupId、version 是可以被继承的,那么还有哪些元素时可以被继承的呢?

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

3.3 依赖管理

​dependencies 元素时可以继承的,所以我们可以将公有的依赖(例如 Spring 的依赖)在父 POM 中定义,但是这样又有一个问题,我们不能保证以后所有的项目都会依赖这些 JAR,例如我们根据需求产生了 blog-pojo 模块,它专门用于对数据库进行实体封装。它就不需要 Spring 的依赖,如果直接在父 POM 中定义 dependencies 是不合理的。

​Maven 提供了 dependencyManagement 元素既能然子模块继承父模块的依赖配置,又能保证子模块依赖使用的灵活性。在 dependencyManagement 元素下的依赖声明并不会引入实际的 JAR,不过它能够约束 dependencies 下的依赖使用。dependencyManagement 声明依赖能够统一项目范围中依赖的版本,当依赖版本在父 POM 中声明以后,子模块声明依赖时无需声明版本,也就不会发生多个模块使用依赖版本不一致的问题。

在 blog-parent 的 POM 进行依赖管理

<project>
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.wennry.blog</groupId>
	<artifactId>blog-parent</artifactId>
	<packaging>pom</packaging>
    <name>Blog Parent</name>
	<version>1.0.0-SNAPSHOT</version>
    
    <properties>
    	<spring.version>2.5.6</spring.version>
    </properties>
    <dependencyManagement>
    	<dependency>
        	<groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencyManagement>
</project>

blog-web 中继承父 POM 的声明信息,就不需要声明依赖版本号:

<project>
	<modelVersion>4.0.0</modelVersion>
    <parent>
    	<groupId>com.wennry.blog</groupId>
        <artifactId>blog-parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <relativePath>../blog-parent/pom.xml</relativePath>
    </parent>
	<artifactId>blog-web</artifactId>
	<packaging>war</packaging>
    <name>Blog Dao</name>
    
    <dependencies>
    	<dependency>
        	<groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
        </dependency>
    </dependencies>
</project>

3.4 插件管理

​类似地,Maven 也提供了 pluginManagement 元素帮助管理插件。在该元素中配置插件并不会造成实际的插件调用行为,当 POM 中配置了真正的 plugin 元素,并且其 groupId 和 artifactId 与 pluginManagement 中配置的插件匹配时,pluginManagement 的配置才会实际影响插件的行为。

父 POM 中进行插件管理

<build>
	<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>
</build>

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

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

​当项目中的多个模块有同样的插件配置时,就应当使用 pluginManagement 进行统一管理。即使各个模块对于同一个插件的具体不尽相同,也应当使用父 POM 的 pluginManagement 统一插件版本。

4. 聚合和继承的关系

​初学者容易将聚合和继承搞混,要说关系他们其实没有任何关系,聚合主要是为了方便快速的构建项目,继承主要是为了消除一些重复的配置,这也就是为什么笔者在上面的案例中,刻意将聚合项目和用于继承的父项目分开的原因,而在日常开发中我们将通常将聚合项目与被继承的父项目放在一起,这样做容易混淆大家对聚合与继承的概念区分。

​对于聚合模块来说,它知道有哪些子模块被聚合,但那些子模块并不知道自己被别人 聚合 了。

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

如果非要说这两个特性的共同点,那么可以看到,聚合 POM 与继承关系中的父 POM 的 packaging 都必须是 pom,同时,聚合模块与继承关系中的父模块除了 POM 之外都没有实际的内容。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

0 文章
0 评论
22 人气
更多

推荐作者

忆伤

文章 0 评论 0

眼泪也成诗

文章 0 评论 0

zangqw

文章 0 评论 0

旧伤慢歌

文章 0 评论 0

qq_GlP2oV

文章 0 评论 0

旧时模样

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文