聚合
当我们有多个子模块的时候,我们应该不会想着去点每个子模块单独去执行构建,而是想通过一条mvn命令实现所有模块的构建。将多个子模块 合并 到一个模块里去的功能,就是聚合。聚合的实现很简单,就是创建一个项目,在这个项目里声明一个<modules>元素 并在其中声明多个模块:
<modules><module>oa-organ</module><module>oa-auth</module><module>oa-flow</module><module>oa-web</module></modules>
此时,做一些test、compile、install等操作都能同时对这些子模块进行操作。
如果使用IDE,建议最好一开始就建立聚合关系,否则需要IDE重新构建这个项目(因为创建一个项目时,大多数都已经分配好了结构)
另外,【聚合】 和 【继承】 是两个概念,【聚合】仅仅只是将多个模块放到一个模块里,然后通过一个模块控制多个模块。聚合有两种目录结构模式:
| 聚合模块的父子目录结构 | 聚合模块的平行目录结构 |
|---|---|
![]() |
![]() |
聚合的小坑
在聚合多个模块的项目下,如果发生了新增子模块、删除子模块操作,请务必重新install <parent>,否则会报找不到依赖:
<坐标> 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如下所示:
<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.zhss.oa</groupId><artifactId>oa-parent</artifactId><version>1.0.0-SNAPSHOT</version><packaging>pom</packaging><name>oa-parent</name><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><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><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>${springframework.release.version}</version></dependency></dependencies></project>
其中重点关注<packaging>pom</packaging>、以及<properties>元素和<dependencies>元素。
父模块只是为了帮助消除重复的配置,因此它本身不需要包含除pom.xml文件以外的项目文件。
然后再看子项目的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><artifactId>oa-flow</artifactId><packaging>jar</packaging><parent><groupId>com.zhss.oa</groupId><artifactId>oa-parent</artifactId><version>1.0.0-SNAPSHOT</version></parent>...</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】而失败。因此有了一种这样的做法:
<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><artifactId>oa-flow</artifactId><packaging>jar</packaging><parent><groupId>com.zhss.oa</groupId><artifactId>oa-parent</artifactId><version>1.0.0-SNAPSHOT</version><relativePath>../pom.xml</relativePath> <!-- 指向父pom.xml --></parent>...</project>
通过<relativePath>元素直接声明【父pom.xml】,那么在单独编译某个模块时就不会有这样的问题了。【Maven】会直接按照这个路径去找到【父pom.xml】。
实战技巧之版本号分歧
当子模块都继承了父模块后,子模块的groupId、version都已经隐式的从父模块里继承过来了,倘若子模块的版本号和父模块的版本号产生了分歧,只需要 在子模块中显式声明 即可覆盖。
聚合与继承的关系
- 对于聚合模块来说,【聚合模块】知道有哪些【被聚合模块】,但是【被聚合模块】不知道这个【聚合模块】
- 对于继承模块来说,【父pom.xml】不知道哪些【子pom.xml】继承它,【子pom.xml】知道自己继承了哪个【父pom.xml】
两者的唯一共同点就是 【聚合模块】 和 【父pom.xml】 的packaging都必须是pom类型,而且它们除了【pom.xml】都没有实际的内容
依赖管理
对于【继承】功能来说,dependencies元素会被无条件全部继承,如果一个模块想要这些依赖,另个模块想要那些依赖:
A工程:依赖com.xuxueli:xxl-job-core、cn.jpush.api:jpush-client、dom4j:dom4jB工程:依赖com.xuxueli:xxl-job-core、commons-codec:commons-codec
(假设这些依赖【父pom.xml】中都有)通过【继承】功能来做的话,A工程里会引入五个依赖,B工程也会引入五个依赖,即使有些依赖它们并用不到。换言之,【继承】功能下的<dependencies>元素太暴力了,强制子模块都继承自己的依赖。所幸【Maven】提供了另一种解决方案——依赖管理。
依赖管理功能是通过 <depdendencyManagement> 元素来实现的,它既提供了一部分的约束性,又提供了一部分的灵活性,让开发人员得到了最高的灵活性。我们在【父pom.xml】中把原先在<dependencies>元素挪到<dependencyManagement>中:
...<!-- parent 父工程 --><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><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>${springframework.release.version}</version></dependency></dependencies></dependencyManagement>...
使用
<dependencyManagement>元素后并不会引入这些依赖,而只是单纯的声明而已
随后在子工程中仍然声明<dependencies>元素(原先使用【继承】不用声明):
<dependencies><!-- spring依赖 --><dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId></dependency><!-- 在子工程中没有声明 spring-webmvc,所以不会引入这个包 --></dependencies>
这个方式的好处是子工程需要哪些依赖就声明哪个依赖即可,但是不用声明**版本**,因为版本已经被【父pom.xml】统一约束起来了(让子模块继承<dependencyManagement>元素来实现的)。所以子工程就能直接继承父工程的版本号。在规范的工程管理中,肯定都是用<dependencyManagement>和<pluginManagemnent>的。
强制约束依赖方的版本号
前面提到了通过【<dependencyManagement>元素 + 继承】来约束子工程的版本号,这是强制约束子工程的版本号。如果想要约束依赖方的版本号(项目和项目之间的依赖)怎么办呢?考虑这样一个场景:
- 你开发了一个第三方的一个组件,就是一个不属于任何一个项目的基础的一个组件,比如基于
**activiti**开发了一个流程审批的封装以后的一个组件。其他人想要使用你这个组件,必然要在<denpendencies>里声明依赖你的组件,倘若这个依赖方没有注意又自己引入了一个activiti1.2(你的组件是activiti1.3),按照依赖调节原则,Maven会去选择依赖方的activiti1.2版本,如此容易造成依赖冲突。
<dependencyManagement>的另外一种玩法就是“导入”:
<!-- common-flow,这是你自己开发的三方组件 -->...
<!-- common-flow-bom,中间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">...<dependencyManagement><dependencies><dependency><groupId>com.zhss.commons</groupId><artifactId>commons-flow</artifactId><version>1.0.0-SNAPSHOT</version></dependency><dependency><groupId>org.activiti</groupId><artifactId>activiti-engine</artifactId><version>6.0.0</version></dependency></dependencies></dependencyManagement></project>
依赖方首先依赖common-flow这个依赖,然后再在自己的pom.xml中声明<dependencyManagement>元素:
<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">...<dependencies>...<dependency><groupId>com.zhss.commons</groupId><artifactId>commons-flow</artifactId></dependency><dependency><groupId>org.activiti</groupId><artifactId>activiti-engine</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>com.zhss.commons</groupId><artifactId>commons-flow-bom</artifactId><version>1.0.0-SNAPSHOT</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement></project>
在<dependencyManagement>元素中声明的<dependency>有两个关注点:
type=pom,表示这个依赖只是一个pom文件scope=import,表示导入这个pom文件(scope=import仅在<dependencyManagement>里生效)
实际上的效果会如下所示:
<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">...<dependencies>...<dependency><groupId>com.zhss.commons</groupId><artifactId>commons-flow</artifactId></dependency><dependency><groupId>org.activiti</groupId><artifactId>activiti-engine</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>com.zhss.commons</groupId><artifactId>commons-flow</artifactId><version>1.0.0-SNAPSHOT</version></dependency><dependency><groupId>org.activiti</groupId><artifactId>activiti-engine</artifactId><version>6.0.0</version></dependency></dependencies></dependencyManagement></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】的目录结构应该按照约定进行使用:
main是放主程序java代码和配置文件java是放你的程序包和包中的java文件resources是放你的java程序中要使用的配置文件test是放测试程序代码和文件的(可以没有),test下的java和resources文件夹都仅在测试阶段生效。pom.xml是maven的核心文件(maven项目必须有)
如果想要自定义配置目录结构,需要这样配置:
<project>
...
<build>
<sourceDirectory>src/java</sourceDirectory>
<testOutputDirectory>...</testOutputDirectory>
<testSourceDirectory>...</testSourceDirectory>
</build>
</project>


