Maven 项目之间的关系
一、继承
1、继承关系介绍
一个 Maven 项目 B 继承另外一个 Maven 项目 A ,那么 A 称为父项目, B 就称为子项目,所谓的项目之间的关系其实就是 pom 与 pom 之间的关系,(因为一个项目有且只有一个 pom.xml ,而 pom.xml 就是描述对应项目的)
使用场景:如果多个子项目中使用的是相同的依赖或插件,此时我们可以把相同的配置抽取到一个父项目中,进行统一的管理,保持一致性.
这里的管理具体是指:
- 子项目继承父项目的依赖,实现统一管理子项目有哪些依赖.
- 父项目管理依赖的版本,实现统一管理子项目依赖的版本.
值得一提的是,这里和全局变量 properties 有些不同, properties 常用来统一管理同一个项目中一类依赖的版本(例如统一管理 Spring 系列的 jar 包版本),而 Maven 继承常用来统一管理多个项目的同一个 jar 包版本
父子工程的项目,子项目最好是放在父项目的目录下,不要放在其它目录下,父项目不做任何逻辑处理,仅仅是为了管理所有的子项目,父项目里面只保留 pom.xml 就可以了.
2、IDEA 创建 Maven 项目
IDEA 创建 3 个 module ,名称分别为 parent、child01、child02
2.1、file---->new---->module
2.2、选择 Maven---->选择合适版本的 SDK---->选择合适的模板
我这里是利用 maven-archetype-quickstart 骨架创建一个 Java 项目
2.3、指定好相应配置
2.4、为了防止创建项目过慢,添加 archetypeCatalog=internal
2.5、child01、child02 创建方式和上面步骤相同,创建好的项目结构如下:
3、配置继承关系
我们这里配置成 child01、child02 都继承 parent 这个项目
这里说一下项目的打包方式, packaging 标签的取值有 jar (默认)、war、pom
打包方式:
jar: java 项目的打包方式,默认值.
war: web 项目的打包方式.
pom: 父项目的专有打包方式,该种方式,本项目不会被打包成 jar 或 war ,项目里 src 目录下代码无效(可删除), pom.xml 有效,只是作为其它项目的父项目使用.
3.1、设置父项目 pom.xml 配置打包方式为 pom
<groupId>com.xiaomaomao.archetype</groupId>
<artifactId>parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
3.2、child01
<groupId>com.xiaomaomao.mavenAnalyse</groupId>
<artifactId>child01</artifactId>
<version>1.0-SNAPSHOT</version>
<!--子项目将来打包成 jar 包-->
<packaging>jar</packaging>
<name>child01</name>
<!--使用 parent 标签表示该项目继承父项目,使用父项目具体坐标来引用父项目 -->
<parent>
<!--父项目的坐标-->
<groupId>com.xiaomaomao.mavenAnalyse</groupId>
<artifactId>parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!--指定父项目的 pom.xml 文件的相对物理路径, ../是上一级目录,
../parent/pom.xml:相对于当前pom.xml ,先到上一级,再到 parent 目录,
找到parent目录下的 pom.xml ,鼠标左键点击如果能跳到 parent 项目的 pom.xml
那么就配置对了 -->
<relativePath>../parent/pom.xml</relativePath>
</parent>
3.3、child02
<groupId>com.xiaomaomao.mavenAnalyse</groupId>
<artifactId>child02</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>child02</name>
<parent>
<groupId>com.xiaomaomao.mavenAnalyse</groupId>
<artifactId>parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
3.4、在 parent 的 pom.xml 中添加一些测试的依赖,child01、child02 pom.xml 中不配置任何依赖
<dependencies>
<!--配置spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-version}</version>
</dependency>
<!--配置webmvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
3.5、依赖继承关系图
从图中可以很明显的看出, child01、child02 继承了 parent 中的依赖
4、父项目对子项目依赖的版本控制
举例: child01 模块使用 log4j 做日志记录,而 child02 模块使用 commons-logging 做日志记录(实际工作中并不会出现此种情况)
那么我们可以这样做
父工程中不引入 log4j 的依赖,也不引入 commons-logging 的依赖, child01 中声明使用 log4j
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
child02 中声明使用 commons-logging
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.0</version>
</dependency>
这样我们就实现了在不同的子工程中引用自己所需要的依赖了,这样看上去很美好,但是呢这里会遗留下一个问题,如果有一天我们需要对各个子模块的依赖进行版本切换,我们只能找到对应的子模块,然后到各个子模块中去切换依赖的版本,这样很不方便,这个时候怎么办呢?
你可能想到了我们不是有父模块吗?让父模块对子模块的依赖进行统一管理就可以了,不错,我们是可以使用父模块,但是还是会有问题,为什么呢?
如果父模块中同时引入 log4j 和 commons-logging 的依赖,确实可以统一对子模块中的依赖进行管理
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.0</version>
</dependency>
但是呢,这样做还是会存在一个问题, child01、child02 都会继承父模块中的依赖,这样原本只需要 log4j 的 child01 就会继承到自己不需要的 commons-logging,同理,child02 也会继承到 log4j ,这样的话
子模块中就很容易产生 jar 包的冲突,并且子模块会继承一些自己压根就不需要的 jar 包,那么怎么办呢?
这里我们可以使用 <dependencyManagement> 标签来解决我们上述的问题,这个标签的作用其实相当于一个对所依赖 jar 包进行版本管理的管理器.( dependencyManagement 里只是声明依赖,并不实现引入)
parent 中 pom.xml
<groupId>com.xiaomaomao.mavenAnalyse</groupId>
<artifactId>parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!--父项目的特有的打包方式,必须声明为 pom-->
<packaging>pom</packaging>
<name>parent</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<!--统一管理 Spring 系列的依赖的版本-->
<spring-version>5.2.8.RELEASE</spring-version>
</properties>
<!--dependencies 中的依赖会被子项目继承-->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring-version}</version>
</dependency>
</dependencies>
<!--dependencyManagement 标签中声明的依赖,只是作为一个 jar 包统一管理的管理器
实际上该标签中声明的依赖不会被引入-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</dependencyManagement>
child 01 中 pom.xml
<groupId>com.xiaomaomao.mavenAnalyse</groupId>
<artifactId>child01</artifactId>
<version>1.0-SNAPSHOT</version>
<!--子项目将来打包成 jar 包,默认值就是 jar-->
<packaging>jar</packaging>
<name>child01</name>
<!--使用 parent 标签表示该项目继承父项目,使用具体坐标定位父项目-->
<parent>
<!--父项目的坐标-->
<groupId>com.xiaomaomao.mavenAnalyse</groupId>
<artifactId>parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!--指定父项目的 pom.xml 文件的相对物理路径, ../是上一级目录,
../parent/pom.xml:相对于当前pom.xml ,先到上一级,再到 parent 目录,
找到parent目录下的 pom.xml ,鼠标左键点击如果能跳到 parent 项目的 pom.xml
那么就配置对了 -->
<relativePath>../parent/pom.xml</relativePath>
</parent>
<dependencies>
<!--依赖的版本已经由父项目通过 dependencyManagement 进行统一管理了
如果没有注明版本:那么就会默认使用父项目中的版本
如果显示的声明了版本,就使用自己声明的版本-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
</dependencies>
child02 中 pom.xml
<groupId>com.xiaomaomao.mavenAnalyse</groupId>
<artifactId>child02</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>child02</name>
<parent>
<groupId>com.xiaomaomao.mavenAnalyse</groupId>
<artifactId>parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<!--子项目中显示的声明依赖版本,那么就使用子项目中声明的版本-->
<version>1.2</version>
</dependency>
</dependencies>
如上配置之后效果如下:
通过 dependencyManagement 标签,我们实现了最终的目的
子工程中相同的模块抽取到父工程的 dependencies 标签中,让每个子工程可以继承
子工程中不同的部分声明在父工程的 dependencyManagement 中,该标签中的依赖不会被父工程引用,但是使用了该标签可以对子模块中不同的依赖进行版本管理,例如:父工程中切换 log4j 和 commons-logging 的版本之后,如果子工程没有显示的声明版本,那么会随着父工程中版本的切换而同步切换
这里面还有一个 pluginManagement 标签,它是父工程对子工程插件版本的统一管理,和 dependencyManagement 的使用相同
二、聚合
聚合:将多个子项目添加到一个父项目中,然后通过对父项目进行操作( Maven 命令),从而实现对所有聚合的子项目的操作
例如:父项目执行 mvn package 操作,子项目也会被打包.
继承和聚合的区别
继承是告诉子项目它的父项目是谁,在哪里,聚合是告诉父项目它的子项目有哪些,分别在哪里
那么怎么实现聚合呢?
在父项目的 pom.xml 中配置
<!--子项目中定位父项目使用的是 pom.xml 来定位的
而父项目定位子项目使用的名字来定位的,为什么不使用
pom.xml 来定位呢?个人猜测可能是同一路径下有多个子
项目,使用 pom.xml 不能准确的定位到是哪个子项目-->
<modules>
<module>../child01</module>
<module>../child02</module>
</modules>
测试聚合后的效果
三、依赖关系
1、依赖关系的传递
项目A---->项目B---->项目C
概念:如果项目 A 依赖于项目 B ,项目 B 依赖于项目 C ,则项目 A 也依赖于项目 C ,这就叫做依赖的传递
如果觉得依赖这个词不是很好理解,可以把依赖翻译为使用,即项目 A 使用项目 B ,项目 B 使用项目 C
下面我们就来演示一个案例: child01 依赖 child02 , child02 依赖 child03 ,child03 依赖 log4j
// child01 的 pom.xml
<!--child01 依赖 child02-->
<dependencies>
<dependency>
<groupId>com.xiaomaomao</groupId>
<artifactId>child02</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
// child02 的 pom.xml
<!--child02 依赖 child03-->
<dependencies>
<dependency>
<groupId>com.xiaomaomao</groupId>
<artifactId>child03</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
// child03 的 pom.xml
<!--child03 依赖 log4j 1.2.12-->
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
</dependencies>
查看依赖关系
从上图中可以看到,实现了依赖传递
2、控制依赖的传递
并不是所有的依赖都会传递
scope 为 compile 的依赖会发生传递
下面这些情况的依赖是不会传递的
- scope 为 test 的依赖不具有依赖传递性,但是具有继承性
- scope 为 provided 的依赖不具有依赖的传递性,但是具有继承性
- 配置 optional 标签为 true 的依赖不具有依赖传递性
修改 child03 pom.xml 中的相关依赖
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.5</version>
<!--optional 的默认值为 false-->
<optional>true</optional>
</dependency>
</dependencies>
查看依赖的传递性
3、依赖传递的原则
使用 Maven 不会出现 jar 包冲突,因为其通过两个原则来保证
3.1、就近原则,依赖的 jar 包距离本项目的层级越近(路径越短),优先级越高.
依赖配置
// child01 中的 pom.xml
<dependencies>
<dependency>
<groupId>com.xiaomaomao</groupId>
<artifactId>child02</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
// child02 中的 pom.xml
<dependencies>
<dependency>
<groupId>com.xiaomaomao</groupId>
<artifactId>child03</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
</dependencies>
// child03 中的 pom.xml
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
查看结果
2、优先声明原则,在同一个 pom.xml 的 dependecy 标签里面, jar 包写在上面,优先级就越高.
依赖配置
// child01 中的 pom.xml
<dependencies>
<dependency>
<groupId>com.xiaomaomao</groupId>
<artifactId>child02</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
// child02 中的 pom.xml
<dependencies>
<dependency>
<groupId>com.xiaomaomao</groupId>
<artifactId>child03</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.xiaomaomao</groupId>
<artifactId>child04</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
// child03 中的 pom.xml
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
// child04 中的 pom.xml
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
</dependencies>
查看结果
3、这两个原则的优先级关系?
首先按照就近原则,如果传递的依赖距离本项目的层级相同,那么再按照优先声明原则传递依赖
4、不要传递的依赖
在实际情况中有些依赖我们不希望传递,那么这些依赖该怎么处理呢?
4.1、如果当前的 pom 我们可以修改,使用 optional 标签即可
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.5</version>
<!--optional 的默认值为 false-->
<optional>true</optional>
</dependency>
4.2、如果当前的 pom.xml 我们不能修改,使用 exclusion 标签
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.2</version>
<exclusions>
<!--使用 exclusion 标签排除不需要的依赖-->
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>