maven项目聚合和父子项目

发布时间 2023-11-10 09:01:49作者: wang_longan

maven项目聚合

聚合项目又称为多模块项目,这种结构的目的是为了统一构建项目,也就是说当对根项目的任何mvn 命令操作,都会相应的执行到每一个被聚合的module项目中,目的是为了方便管理多个项目的编译打包等操作。

想象一下,如果你创建了10个项目,如果你要对这10个项目进行 mvn install操作(将项目发布到本地仓库),难道要单独的进行10遍操作么?这样做显然是枯燥且乏味的。

项目构建工具如maven提供的聚合特性就是应对该场景的,只需要对根项目操作一次命令后,每个被聚合的模块项目均会自动执行一遍该命令,十分方便。

项目聚合样例:

根项目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>mvn-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>

    <modules>
        <module>module1</module>
        <module>module2</module>
    </modules>

    <properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

根项目的pom文件里有必须添加两个标签:

<packaging>pom</packaging>是一种打包类型的配置。它表示项目不会生成可执行的JAR或WAR文件,而是作为一个父项目或聚合项目存在,用于管理子模块的构建和依赖关系

<modules>
        <module>module1</module>
        <module>module2</module>
 </modules>

Maven项目中的<modules>元素,它指定了当前项目的子模块列表。在这个例子中,项目有两个子模块,分别是module1和module2。这意味着这个项目是一个聚合项目,它管理着多个子模块的构建和依赖关系。当你运行Maven命令时,Maven会自动构建所有的子模块。

子模块并不知道根项目的存在,新建子模块也非常简单

这样只需在根项目的pom文件路径下执行mvn命令,那么mvn命令会自动在子模块中都执行一遍,比如打包package,只需在根项目中执行mvn clean package,两个模块module-demo1和module-demo2均会执行一遍命令mvn clean package。

父子项目

父子模块这种项目结构,本质上就是继承关系。上边聚合模块结构没有上下级区分,但这里的父子模块就要区分上下级了(这里的上下级不是指文件目录的上下级),子模块会继承父模块的相关依赖配置。

继承在java里的好处就是子类可以使用父类的属性和方法,减少代码冗余。
maven这里同样也是这个好处:

  1. 父模块的groupId和version会传递到子模块,父子模块作为一个整体,子模块不需要再声明,仅需要指定自己独有的artifactId即可,当然如果你依赖的父模块和你的项目不是同一个groupId时,你也可以指定子模块的groupId和version;
  2. 父模块的依赖配置会传递到子模块,子模块不需要再单独引入依赖;
  3. 父模块可以管理依赖的版本,子模块可以自由灵活的选择需要的依赖,不需要再关心版本的问题。

在聚合项目那里说过,被聚合的模块根本无法感知到根模块的存在。父子模块正好相反,父模块根本无法感知到哪个子模块把它当作爸爸。

搭建父子模块

这里搭建一个单纯的父子模块,也就是不在父模块中配置module标签,仅在子模块中指定它的父模块是谁。

1.创建父模块
父子模块中,父模块一般作为子模块依赖的管理,它只需要定义好依赖的版本和必须的依赖即可。

父模块的打包方式必须为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>mvn-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <packaging>pom</packaging>

    <properties>
        <java.version>17</java.version>
        <commons.lang3.version>3.12.0</commons.lang3.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>


    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>${commons.lang3.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>2.7.15</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.7.15</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

可以看到父模块的pom文件有三个特殊标签

<packaging>pom</packaging>

 package标签作用和上面讲述的相同

<dependencyManagement>

<dependencyManagement>标签在Maven项目中的作用是管理依赖项的版本。它允许您在父项目中集中定义依赖项的版本,然后在子项目中继承这些版本定义而无需重复声明。

通过在父项目的pom.xml文件中使用<dependencyManagement>标签,您可以列出所有依赖项及其所需的版本。然后,在子项目的pom.xml文件中,您可以引用这些依赖项而无需指定版本号。这样,当您更新父项目中的依赖项版本时,所有子项目都会自动继承这些变化。

这种集中管理依赖项版本的方式有助于确保项目中使用的依赖项保持一致,并简化了项目配置。此外,它还可以避免不同子项目中使用不同版本的依赖项导致的冲突和问题。

<dependencyManagement>标签的作用是集中管理Maven项目中的依赖项版本,以确保项目的一致性和简化配置。

<dependencies>

 在Maven父子项目中,父项目的<dependencies>标签用于管理整个项目组的依赖项。它允许在父项目中声明和管理所有子模块共享的依赖关系。也就是说在父项目的pom文件中配置的<dependencys>依赖项,会被所有子项目全部继承。

2、创建两个子项目child-demo1和child-demo2

两个子项目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>mvn-demo1</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>

    <artifactId>child-demo2</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

 

<?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>mvn-demo1</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>

    <artifactId>child-demo1</artifactId>

    <name>child-demo1</name>
    <description>child-demo1</description>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
    </dependencies>
</project>

可以看到这两个子项目的pom文件的<parent>标签均填写的父项目的gav,在子项目pom文件中,不需要再填写<group>和<version>标签,因为子项目会自动继承父项目的这两个标签的值。

当前如果子项目想适用和父项目不同的g和v,则添加该两个标签和想要的值即可。

child-demo1子项目和child-demo2项目会自动继承父项目中<dependencis>标签中的所有依赖,所以尽量在这两个子项目的pom文件中,即使没有显式的配置junit和spring-boot-starter-test这两个依赖项,

但是子项目也会直接从父项目中将这两个依赖项配置项继承过来。

可以看到child-demo1子项目的spring-boot-start-web和commons-lang3两个依赖项并没有配置<version>标签,因为在父项目中的<dependencyManagement>标签中已经声明了这两个依赖项的版本,子项目会直接将<dependencyManagement>标签中配置的版本信息,范围等继承过来,但子项目中如果不配置该依赖项,则不会继承父项目中<dependencyManagement>标签配置的数据。<dependencyManagement>标签仅起到版本管理的作用。

配置完成后,可以分别在child-demo1子项目和child-demo2子项目的pom文件所在路径直接mvn命令直接打包即可。

可以看到父子项目是起到减少子项目中pom文件的重复配置和版本管理方便的目的。

聚合项目和父子项目同时使用

可以看到上面的父子项目,在打包时,需要分别对两个子项目进行打包,这时,如果想一次打包两个或者多个子项目,就要用到上面讲的maven聚合功能就能用到了。

只需在父项目的pom文件中添加<modules>标签,然后将需要聚合的子项目填进来即可

<modules>
    <module>child-demo1</module>
    <module>child-demo2</module>
</modules>

 完整的父项目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>mvn-demo1</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <packaging>pom</packaging>

    <name>mvn-demo1</name>
    <description>mvn-demo1</description>

    <modules>
        <module>child-demo1</module>
        <module>child-demo2</module>
    </modules>

    <properties>
        <java.version>17</java.version>
        <commons.lang3.version>3.12.0</commons.lang3.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>


    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>${commons.lang3.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>2.7.15</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.7.15</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

子项目不需要修改,即可实现在父项目的pom文件路径下执行maven命令,打包两个或多个子项目的给功能。

可以看出maven聚合功能是为了方便管理和打包多个项目,而maven的父子项目则是利用了和java继承思想类似的思想,让子项目继承父项目的maven配置,如版本,依赖项,版本控制等,这样子项目就可以复用父项目中的配置,起到减少配置冗余的目的。

可以看到直接在父项目的pom文件所在路径执行mvn clean package命令,两个子项目就都打包成功了。

总结

在我们实际开发中,一般都是将聚合和父子这两种关系混合使用,这里我拆分说,只是为了分别去理解它们各自的作用。

不论父子模块还是聚合模块,根模块的打包方式都必须是pom,下面的模块可以是jar或者war这两种打包方式。

聚合模块这种项目结构,仅仅是为了方便统一管理操作所有的模块。根模块和它内部<module> 标签内的模块是一个整体,项目目录层级上可以不要求一定上下级,但必须保持一定的层级联系。

父子模块这种项目结构,仅仅是为了方便子模块对依赖的管理,子模块通过<parent> 标签,引入父模块的配置去约束子模块的依赖版本。也可以抽出共同的依赖给到父模块,子模块去继承它,减少每个子模块冗余的配置。项目层级没有要求,你可以引入任意的依赖当作父模块,比如spring-boot-starter。

当然混合使用才能发挥它们最大的优势!

参考文章