• maven聚合和继承


    一、什么是maven的聚合和继承&why

    随着技术飞速发展,各类用户对软件的要求越来越高,软件也变得越来越复杂。
    软件设计人员往往会采用各种方式对软件划分模块,已得到更加清晰的设计及更高的复用性。

    当把Maven应用到实际项目中的时候,也需要将项目分成不同的模块。
    Maven的聚合特性能够把项目的各个模块聚合在一起构建,而maven的继承特性则能帮助抽取各个模块相同的依赖和插件等配置。

    二、聚合

    例如:现在有一个商城服务下面有两个模块,item-service和cart-service。如果我们想要一次构建两个项目(模块),而不是到两个模块的目录下分别执行mvn命令。
    为了能够一次构建item-service和cart-service两个模块,我们需要格外的名为hmall的模块,然后通过该模块构建整个项目的所有模块。
    hmall作为一个maven项目,它也必须有它自己的POM。
    下面是hmall的pom.xml内容:
    在这里插入图片描述
    上面的POM依旧使用了商品服务共同的groupId com.heima,artifactId为独立的hamll,版本也与其他两个模块一致。

    但是这里的packaging,值为POM。而item-service和cart-service的packaging为jar。对于聚合模块来说,其打包方式packaging的值为POM,否则就无法构建。

    而modules这是实现聚合的最核心的配置。用户可以通过在一个打包方式为pom的Maven项目中声明任意数量的module元素来实现模块的聚合。

    这里每一个module的值都是一个当前POM的相对目录。例如:hmall的POM的路径为D:\WorkSpace\hmall\pom.xml那么item-service就对应了目录D:\WorkSpace\hmall\item-service。这两个目录各自包含了pom.xml、src/main/java/、src/test/java等内容。离开hmall也能够独立构建。

    为了方便用户构建项目、通常将聚合模块放在项目目录的最顶层,其他模块作为聚合模块的子目录存在,这样当用户得到源码的时候,第一眼发现的就是聚合模块的POM不用从多个模块中去寻找聚合模块来构建整个项目。

    • hmall
      • cart-service
        • pom.xml
      • item-service
        • pom.xml
      • pom.xml

    当然也可以是平行的目录结构

    • hmall
      • pom.xml
    • cart-service
      • pom.xml
    • item-service
      • pom.xml

    如果使用平行目录,聚合模块的POM也需要做相应的修改

    
    ../item-service
    
    
    • 1
    • 2
    • 3

    从聚合模块运行mvn clean install命令得到的输出
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    maven首先解析聚合模块的POM,分析要构建的模块、并计算出一个反应堆构建顺序,然后根据这个顺序依次构建各个模块。

    三、继承

    面向对象设计中,可以建立一种类的父子结构,然后父类中声明一些字段和方法供子类继承。这样可以做到“一处声明,多处使用”。

    类似的在maven世界中,也有类似的机制能让我们抽取出重复的配置。做到一处声明,多处使用这就是POM的继承。

    我们创建POM的父子结构,然后在父POM中声明一些配置供子POM继承。

    如果A工程继承B工程,则代表A工程默认依赖B工程依赖的所有资源,且可以应用B工程中定义的所有资源信息。

    被继承的工程(B工程)只能是POM工程。

    注意:在父项目中放在中的内容时不被子项目继承,不可以直接使用

    放在中的内容主要目的是进行版本管理。里面的内容在子项目中依赖时坐标只需要填写

    即可。(注意:如果子项目不希望使用父项目的版本,可以明确配置version)。

    继续以商城服务为基础。pom.xml文件如下:
    在这里插入图片描述
    需要注意的一点,它的packaging为pom,这一点与聚合模块一样,作为父模块的POM,其打包类型也必须为pom,由于父模块只是为了帮助消除配置的重复,因此它本身不包含POM之外的项目文件,也不需要src/maini/java之类的文件夹。
    有了父模块,来看看继承父模块的子模块。将item-service的POM修改如下:
    在这里插入图片描述
    上述POM中使用parent元素声明父模块快,parent下的元素groupId、artifactId和version指定了父模块的坐标,元素relativePath表示父模块POM的相对路径,该例中的hmall/pom.xml表示父POM的位置在与item-serice/pom.xml上一层的目录下。
    当项目构建时,maven会首先更具relativePath检查父POM,如果找不到,再从本地仓库中查找。relativePath的默认值为../pom.xml,也就是说,maven默认父POM在上一层目录下。

    1.继承的资源

    • groupId:项目组ID
    • version:项目版本
    • description:项目的描述信息
    • organization:项目的组织信息
    • inceptionYear:项目的创始年份
    • url:项目的URL地址
    • developers:项目的开发者信息
    • contribution:项目的贡献者信息
    • distributionManagement:项目的部署配置
    • issueManagement:项目的缺陷跟踪系统信息
    • ciManagement:项目的持续继承系统信息
    • scm:项目的版本控制系统信息
    • mailingLists:项目的邮件列表信息
    • properties:自定义的Maven属性
    • dependencies:项目的依赖配置
    • dependencyManagement:项目的依赖管理配置
    • repositories:项目的仓库配置
    • build:包括项目的硬盘吗目录配置、输出目录配置、插件配置、插件管理配置等。
    • reporting:包括项目的报告输出目录配置、报告插件配置等。

    2.依赖管理

    可继承元素列表包含了dependencies元素,说明依赖是会被继承的。在子模块item-service和cart-service同时依赖了lombok,spring-boot-starter-test等等,因此可以将这些依赖配置放到父模块hmall中,两个子模块就能够移除这些依赖,简化配置。

    maven提供的dependencyManagement元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性。在dependencyManagement元素下的依赖声明不会引入实际的依赖,不过它能够约束dependencies下的依赖使用。例如:可以在hmall加入这样的dependencyManagement配置如:

    
    
        4.0.0
    
        com.heima
        hmall
        pom
        1.0.0
        
            hm-common
            item-service
            cart-service
        
    
        
            org.springframework.boot
            spring-boot-starter-parent
            2.7.12
            
        
    
        
            11
            11
            UTF-8
            UTF-8
            1.18.20
            2021.0.3
        
    
        
        
            
                
                
                    org.springframework.cloud
                    spring-cloud-dependencies
                    ${spring-cloud.version}
                    pom
                    import
                
                 
            
                org.springframework.boot
                spring-boot-starter-test
                test
            
            
        
        
            
            
                org.projectlombok
                lombok
                ${org.projectlombok.version}
            
        
    
        
            
                
                    org.apache.maven.plugins
                    maven-compiler-plugin
                    
                        1.8
                        1.8
                        UTF-8
                    
                
            
        
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    首先父POM将lombok依赖的版本以Maven变量的形式提取了出来,不仅消除了一些重复也使得各依赖的版本处于更加明显的位置。
    这是使用的dependencyManagement声明的依赖既不会给item-service引入依赖,也不会给它的子模块引入一依赖。不过这段配置是会被继承的。
    现在修改item-service的POM如:

    
    
        4.0.0
        
            com.heima
            hmall
            1.0.0
        
        item-service
    
        
            
            
                com.heima
                hm-common
                1.0.0
            
            
            
                org.springframework.boot
                spring-boot-starter-web
            
             
            
                org.springframework.boot
                spring-boot-starter-test
            
        
        
            ${project.artifactId}
            
                
                    org.springframework.boot
                    spring-boot-maven-plugin
                
            
        
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    上述的POM中的依赖配置较原来简单了一些,所有的springframework依赖只配置了groupId和artifactId,省去了version,而Junit依赖不仅省去了version,还省去了依赖范围scope。这些信息可以省略是因为item-service继承了hamll中的dependencyManagement配置。完整的依赖声明已经包含在父POM中,子模块只需要配置简单的groupId和artifactId就能获得对应的依赖信息,从而引入正确的依赖。

    使用这种依赖管理机制似乎不能减少太多的POM配置,但强烈推荐采用这种方法。

    主要原因是在于父POM中声明之后,子模块在使用依赖的使用就无须声明版本,也就不会发生多个子模块使用依赖版本不一致的情况。这可以降低依赖冲突的几率。

    如果子模块不生命依赖的使用,即使依赖已经在父POM的dependencyManagement中声明了,也不会产生任何实际的效果。

    补充依赖范围为import的依赖范围,该依赖范围只在dependencyManagement元素下才有效果,使用该范围的依赖通常指向一个POM,作用是将目标POM的dependencyManagement配置导入并合并到当前POM的dependencyManagement元素中。例如想要在另一个模块中使用与上面dependencyManagement的一样的配置,除了直接配置拥有依赖管理的依赖或者继承这两种方式之外,还可以使用import范围依赖,将这一配置导入。如:

    
            
            
                com.heima
                hmall
        		1.0.0
        	    pom
        	    import
            
            
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3.插件管理

    maven也提供了pluginManagement元素帮助管理插件。该元素中配置的依赖不会造成实际的插件调用行为,当POM中配置了真正的plugin元素,并且其groupId和artifactId与pluginManagement中配置的插件匹配时,pluginManagement的配置才会影响实际的插件行为。
    例如:
    配置了maven-source-plugin,将其jar-no-fork目标绑定到了verity生命周期阶段,以生成项目源码包。如果一个项目中有很多子模块,并且需要得到所有这些模块的源码包,那么更好的方法是父POM中使用pluginManagement配置插件。
    如:

    
            
                
                    
                        org.apache.maven.plugins
                        maven-source-plugin
                        2.2.1
                        
                            
                                attach-sources
                                verify
                                
                                    jar-no-fork
                                
                            
                        
                    
                
            
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

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

     
            
                
                    org.apache.maven.plugins
                    maven-compiler-plugin
                
            
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    子模块声明使用了maven-source-plugin插件,同时又继承了父模块的pluginManagement配置。如果子模块不需要使用父模块中pluginManagement,可以尽管将其忽略,如果子模块需要不同的插件配置,则可以执行配置以覆盖父模块的pluginManagement配置。

    四、聚合与继承的关系

    多模块maven项目中聚合与继承其实是两个概念,其目的完全不同。前者主要是为了方便快速构建项目,后者主要是为了消除重复配置。
    对于聚合模块来说,它知道有哪些被聚合的模块,但那些被聚合的模块不知道这个聚合模块的存在。
    对于继承关系的父POM来说,他不知道有哪些子模块继承于它,但那些子模块都必须知道自己的父POM是什么。
    共同点:
    聚合POM与继承关系的父POM的packaging都必须是pom,同时聚合模块与继承关系的父模块除了POM之外都没有实际内容。
    在现有的实际项目中,往往一个POM即是聚合POM,又是父POM。

    五、约定优于配置

    标准的重要性已经不用过于强调,想象一下,如果不是所有程序员都是基于HTTP协议开发Web应用,互联网会乱成什么样。各个版本的IE,FireFox等浏览器之间的差异已经让很多开发者头疼不已。
    java成功的重要原因之一就是它能够屏蔽大部分操作系统的差异,XML流行的原因之一是所有语言都接受它。而maven提倡的“约定优于配置”这是maven最为核心的设计理念之一。
    那为什么要使用约定而不是自己更灵活的配置呢?原因之一是使用约定可以大量减少配置。

    六、反应堆

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

    1.反应堆的构件顺序

    如:hmall的聚合配置如下:
    在这里插入图片描述
    构建完成之后会有这样的输出:
    在这里插入图片描述
    可以看出反应堆的构建顺序,依次为hmall、hm-common、item-service、cart-service。
    实际的构建顺序是:
    maven按顺序读取POM,如果该POM没有依赖模块,那么就构建该模块,否则就先构建其依赖模块,如果该依赖模块还依赖于其他模块,则进一步限购键依赖的依赖。
    模块间的依赖关系会将反应堆构成一个有向非循环图,各个模块是该图的节点,依赖关系构成有向边。这个图不允许出现循环,因此,当出现模块A依赖与B,而B依赖于A的情况时,maven会报错。

    七、裁剪反应堆

    有时会需要仅仅构建完整反应堆中的某些个模块,maven提供很多的命令行选项支持裁剪反应堆,输出mvn-h可以看到这些选项。

  • 相关阅读:
    SaaSBase:什么是BoardMix?
    鱼骨图:简单,但你不得不会(附模板直接下载)
    J2EE从入门到入土06.XML配置文件的读取
    Explain 关键字
    Vue+iView中在table表格中使用render函数嵌入input,实现双向数据绑定
    用了两周开源堡垒机OneTerm,我有一些建议
    resnet_v1.resnet_v1()
    机器视觉工程师能不能去海康做机器视觉?
    【m98】接收udp包到变为CopyOnWriteBuffer的rtp包及call模块传递的过程
    变电站机器人的控制部分
  • 原文地址:https://blog.csdn.net/qq_41428418/article/details/133927406