聚合

当我们有多个子模块的时候,我们应该不会想着去点每个子模块单独去执行构建,而是想通过一条mvn命令实现所有模块的构建。将多个子模块 合并 到一个模块里去的功能,就是聚合。聚合的实现很简单,就是创建一个项目,在这个项目里声明一个<modules>元素 并在其中声明多个模块:

  1. <modules>
  2. <module>oa-organ</module>
  3. <module>oa-auth</module>
  4. <module>oa-flow</module>
  5. <module>oa-web</module>
  6. </modules>

此时,做一些testcompileinstall等操作都能同时对这些子模块进行操作。

如果使用IDE,建议最好一开始就建立聚合关系,否则需要IDE重新构建这个项目(因为创建一个项目时,大多数都已经分配好了结构)

另外,【聚合】 和 【继承】 是两个概念,【聚合】仅仅只是将多个模块放到一个模块里,然后通过一个模块控制多个模块。聚合有两种目录结构模式:

聚合模块的父子目录结构 聚合模块的平行目录结构
image.png image.png

聚合的小坑

在聚合多个模块的项目下,如果发生了新增子模块、删除子模块操作,请务必重新install <parent>,否则会报找不到依赖:

  1. <坐标> was cached in the local repository, resolution will not be reattempted until the update interval of <远程仓库名字> has elapsed or updates are forced

继承

如果有许多公共的组件每个模块都需要使用,那么在所有的子模块中都得重复定义很多遍,但是若有了【继承】,就只需要定义一遍。子项目都可以通过<parent>元素继承父模块,从而实现所有的依赖、属性都能够被子模块直接继承。比如父项目pom.xml如下所示:

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4. <groupId>com.zhss.oa</groupId>
  5. <artifactId>oa-parent</artifactId>
  6. <version>1.0.0-SNAPSHOT</version>
  7. <packaging>pom</packaging>
  8. <name>oa-parent</name>
  9. <properties>
  10. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  11. <springframework.release.version>3.2.8.RELEASE</springframework.release.version>
  12. <jackson.version>2.9.2</jackson.version>
  13. </properties>
  14. <dependencies>
  15. <!-- spring依赖 -->
  16. <dependency>
  17. <groupId>org.springframework</groupId>
  18. <artifactId>spring-core</artifactId>
  19. <version>${springframework.release.version}</version>
  20. </dependency>
  21. <dependency>
  22. <groupId>org.springframework</groupId>
  23. <artifactId>spring-beans</artifactId>
  24. <version>${springframework.release.version}</version>
  25. </dependency>
  26. <dependency>
  27. <groupId>org.springframework</groupId>
  28. <artifactId>spring-webmvc</artifactId>
  29. <version>${springframework.release.version}</version>
  30. </dependency>
  31. </dependencies>
  32. </project>

其中重点关注<packaging>pom</packaging>、以及<properties>元素和<dependencies>元素。

父模块只是为了帮助消除重复的配置,因此它本身不需要包含除pom.xml文件以外的项目文件。

然后再看子项目的pom.xml如下所示:

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4. <artifactId>oa-flow</artifactId>
  5. <packaging>jar</packaging>
  6. <parent>
  7. <groupId>com.zhss.oa</groupId>
  8. <artifactId>oa-parent</artifactId>
  9. <version>1.0.0-SNAPSHOT</version>
  10. </parent>
  11. ...
  12. </project>

这个子项目的pom.xml中继承了oa-parent,那么oa-parent中的所有<dependencies>元素、<properties>元素都会被这个子项目oa-flow继承。

哪些元素可以被子模块继承

可被子模块继承的元素
**groupId** 项目组ID(重要) ciManagement 项目持续继承配置
**version** 项目版本(重要) scm 项目版本控制配置
description 项目描述信息 mailingLists 邮件列表信息
organization 项目组织信息 proerties 自定义的Maven属性
inceptionYear 项目的创始年份 dependencies 项目的依赖配置
url 项目的URL地址 dependencyManagement 项目的依赖管理配置
developers 项目开发者信息 repositories 项目的仓库配置
contributors 项目贡献者信息 build 项目的源码目录、输出目录、插件配置、插件管理等等
distributionManagement 项目的部署配置 reporting
issueManagement 缺陷跟踪系统配置

实战技巧之单独编译子模块

如果我们有很多个子模块(未安装到本地仓库),但是我只想编译一个子模块时,因为父模块还没有安装到本地仓库中,所以就会因为找不到【父pom.xml】而失败。因此有了一种这样的做法:

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4. <artifactId>oa-flow</artifactId>
  5. <packaging>jar</packaging>
  6. <parent>
  7. <groupId>com.zhss.oa</groupId>
  8. <artifactId>oa-parent</artifactId>
  9. <version>1.0.0-SNAPSHOT</version>
  10. <relativePath>../pom.xml</relativePath> <!-- 指向父pom.xml -->
  11. </parent>
  12. ...
  13. </project>

通过<relativePath>元素直接声明【父pom.xml】,那么在单独编译某个模块时就不会有这样的问题了。【Maven】会直接按照这个路径去找到【父pom.xml】。

实战技巧之版本号分歧

当子模块都继承了父模块后,子模块的groupIdversion都已经隐式的从父模块里继承过来了,倘若子模块的版本号和父模块的版本号产生了分歧,只需要 在子模块中显式声明 即可覆盖。

聚合与继承的关系

  • 对于聚合模块来说,【聚合模块】知道有哪些【被聚合模块】,但是【被聚合模块】不知道这个【聚合模块】
  • 对于继承模块来说,【父pom.xml】不知道哪些【子pom.xml】继承它,【子pom.xml】知道自己继承了哪个【父pom.xml】

两者的唯一共同点就是 【聚合模块】 和 【父pom.xml】 的packaging都必须是pom类型,而且它们除了【pom.xml】都没有实际的内容
image.png

依赖管理

对于【继承】功能来说,dependencies元素会被无条件全部继承,如果一个模块想要这些依赖,另个模块想要那些依赖:

  • A工程:依赖com.xuxueli:xxl-job-corecn.jpush.api:jpush-clientdom4j:dom4j
  • B工程:依赖com.xuxueli:xxl-job-corecommons-codec:commons-codec

(假设这些依赖【父pom.xml】中都有)通过【继承】功能来做的话,A工程里会引入五个依赖,B工程也会引入五个依赖,即使有些依赖它们并用不到。换言之,【继承】功能下的<dependencies>元素太暴力了,强制子模块都继承自己的依赖。所幸【Maven】提供了另一种解决方案——依赖管理。
依赖管理功能是通过 <depdendencyManagement> 元素来实现的,它既提供了一部分的约束性,又提供了一部分的灵活性,让开发人员得到了最高的灵活性。我们在【父pom.xml】中把原先在<dependencies>元素挪到<dependencyManagement>中:

  1. ...
  2. <!-- parent 父工程 -->
  3. <dependencyManagement>
  4. <dependencies>
  5. <!-- spring依赖 -->
  6. <dependency>
  7. <groupId>org.springframework</groupId>
  8. <artifactId>spring-core</artifactId>
  9. <version>${springframework.release.version}</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework</groupId>
  13. <artifactId>spring-beans</artifactId>
  14. <version>${springframework.release.version}</version>
  15. </dependency>
  16. <dependency>
  17. <groupId>org.springframework</groupId>
  18. <artifactId>spring-webmvc</artifactId>
  19. <version>${springframework.release.version}</version>
  20. </dependency>
  21. </dependencies>
  22. </dependencyManagement>
  23. ...

使用<dependencyManagement>元素后并不会引入这些依赖,而只是单纯的声明而已

随后在子工程中仍然声明<dependencies>元素(原先使用【继承】不用声明):

  1. <dependencies>
  2. <!-- spring依赖 -->
  3. <dependency>
  4. <groupId>org.springframework</groupId>
  5. <artifactId>spring-core</artifactId>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.springframework</groupId>
  9. <artifactId>spring-beans</artifactId>
  10. </dependency>
  11. <!-- 在子工程中没有声明 spring-webmvc,所以不会引入这个包 -->
  12. </dependencies>

这个方式的好处是子工程需要哪些依赖就声明哪个依赖即可,但是不用声明**版本**,因为版本已经被【父pom.xml】统一约束起来了(让子模块继承<dependencyManagement>元素来实现的)。所以子工程就能直接继承父工程的版本号。在规范的工程管理中,肯定都是用<dependencyManagement><pluginManagemnent>的。

强制约束依赖方的版本号

前面提到了通过【<dependencyManagement>元素 + 继承】来约束子工程的版本号,这是强制约束子工程的版本号。如果想要约束依赖方的版本号(项目和项目之间的依赖)怎么办呢?考虑这样一个场景:

  • 你开发了一个第三方的一个组件,就是一个不属于任何一个项目的基础的一个组件,比如基于**activiti**开发了一个流程审批的封装以后的一个组件。其他人想要使用你这个组件,必然要在<denpendencies>里声明依赖你的组件,倘若这个依赖方没有注意又自己引入了一个activiti1.2(你的组件是activiti1.3),按照依赖调节原则,Maven会去选择依赖方的activiti1.2版本,如此容易造成依赖冲突。

<dependencyManagement>的另外一种玩法就是“导入”:

  1. <!-- common-flow,这是你自己开发的三方组件 -->
  2. ...
  1. <!-- common-flow-bom,中间POM -->
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. ...
  5. <dependencyManagement>
  6. <dependencies>
  7. <dependency>
  8. <groupId>com.zhss.commons</groupId>
  9. <artifactId>commons-flow</artifactId>
  10. <version>1.0.0-SNAPSHOT</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.activiti</groupId>
  14. <artifactId>activiti-engine</artifactId>
  15. <version>6.0.0</version>
  16. </dependency>
  17. </dependencies>
  18. </dependencyManagement>
  19. </project>

依赖方首先依赖common-flow这个依赖,然后再在自己的pom.xml中声明<dependencyManagement>元素:

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3. ...
  4. <dependencies>
  5. ...
  6. <dependency>
  7. <groupId>com.zhss.commons</groupId>
  8. <artifactId>commons-flow</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.activiti</groupId>
  12. <artifactId>activiti-engine</artifactId>
  13. </dependency>
  14. </dependencies>
  15. <dependencyManagement>
  16. <dependencies>
  17. <dependency>
  18. <groupId>com.zhss.commons</groupId>
  19. <artifactId>commons-flow-bom</artifactId>
  20. <version>1.0.0-SNAPSHOT</version>
  21. <type>pom</type>
  22. <scope>import</scope>
  23. </dependency>
  24. </dependencies>
  25. </dependencyManagement>
  26. </project>

<dependencyManagement>元素中声明的<dependency>有两个关注点:

  • type=pom,表示这个依赖只是一个pom文件
  • scope=import,表示导入这个pom文件(scope=import仅在<dependencyManagement>里生效)

实际上的效果会如下所示:

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3. ...
  4. <dependencies>
  5. ...
  6. <dependency>
  7. <groupId>com.zhss.commons</groupId>
  8. <artifactId>commons-flow</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.activiti</groupId>
  12. <artifactId>activiti-engine</artifactId>
  13. </dependency>
  14. </dependencies>
  15. <dependencyManagement>
  16. <dependencies>
  17. <dependency>
  18. <groupId>com.zhss.commons</groupId>
  19. <artifactId>commons-flow</artifactId>
  20. <version>1.0.0-SNAPSHOT</version>
  21. </dependency>
  22. <dependency>
  23. <groupId>org.activiti</groupId>
  24. <artifactId>activiti-engine</artifactId>
  25. <version>6.0.0</version>
  26. </dependency>
  27. </dependencies>
  28. </dependencyManagement>
  29. </project>

插件管理

【Maven】不仅提供了【依赖管理】功能来管理依赖还提供了【插件管理】来帮助管理插件。同<dependencyManagement>一样,<pluginManagement>里的配置不会造成实际的引入和插件调用。

<!-- parent 父pom -->
<build>
    ...
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <phase>verify</phase>
                        <goals>
                            <goal>jar-no-fork</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <outputDirectory>${project.build.outputDirectory}</outputDirectory>
                </configuration>
            </plugin>
        </plugins>
    </pluginManagement>
</build>

这样只是声明了这个插件的属性,并不对产生任何实际的影响。如果想要真正使用它,就需要在<build><plugins>里声明对应的<plugin>即可使用:

<!-- 子工程 -->
<build>
    ...
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
        </plugin>
    </plugins>
</build>

此时,在子工程中只需要定义一下这个插件,就可以使用【父pom.xml】里配置好的插件。如果不想使用父模块的<pluginManagement>,那么直接在里面覆盖即可。

如果项目中多个模块都有同样的插件配置时,最好将配置都移到【父pom.xml】的<pulginManagement>元素中。即使各个模块对同一插件的具体配置不尽相同,也应当在【父pom.xml】的<pluginManagement>元素里统一声明插件版本。这样更易维护和维持稳定。

设置变量统一版本号

简单来说就是通过给定一个变量,让所有的依赖的<version>设置为此变量,从而实现修改一个变量,就能让所有依赖的版本变更。

<!-- parent -->
<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">
      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <springframework.release.version>3.2.8.RELEASE</springframework.release.version>
        <jackson.version>2.9.2</jackson.version>
      </properties>

      <dependencyManagement>
          <dependencies>
          <!-- spring依赖 -->
              <dependency>  
                <groupId>org.springframework</groupId>  
                <artifactId>spring-core</artifactId>  
                <version>${springframework.release.version}</version>
          </dependency>
              <dependency>  
                <groupId>org.springframework</groupId>  
                <artifactId>spring-beans</artifactId>  
                <version>${springframework.release.version}</version>
          </dependency>
      </dependencies>
    </dependencyManagement>
</project>

拓展知识

正常来说【Maven】的目录结构应该按照约定进行使用:
image.png

  • main是放主程序java代码和配置文件
  • java是放你的程序包和包中的java文件
  • resources是放你的java程序中要使用的配置文件
  • test是放测试程序代码和文件的(可以没有),test下的javaresources文件夹都仅在测试阶段生效。
  • pom.xmlmaven的核心文件(maven项目必须有)

如果想要自定义配置目录结构,需要这样配置:

<project>
  ...
  <build>
    <sourceDirectory>src/java</sourceDirectory>
    <testOutputDirectory>...</testOutputDirectory>
    <testSourceDirectory>...</testSourceDirectory>
  </build>
</project>