一、继承

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

Java Maven 子项目使用jar 包里依赖版本 maven子项目依赖父项目_xml

2.2、选择 Maven---->选择合适版本的 SDK---->选择合适的模板

我这里是利用 maven-archetype-quickstart 骨架创建一个 Java 项目

Java Maven 子项目使用jar 包里依赖版本 maven子项目依赖父项目_jar_02

2.3、指定好相应配置

Java Maven 子项目使用jar 包里依赖版本 maven子项目依赖父项目_ci_03

2.4、为了防止创建项目过慢,添加 archetypeCatalog=internal

Java Maven 子项目使用jar 包里依赖版本 maven子项目依赖父项目_ci_04

2.5、child01、child02 创建方式和上面步骤相同,创建好的项目结构如下:

Java Maven 子项目使用jar 包里依赖版本 maven子项目依赖父项目_xml_05

Java Maven 子项目使用jar 包里依赖版本 maven子项目依赖父项目_ci_06

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、依赖继承关系图

Java Maven 子项目使用jar 包里依赖版本 maven子项目依赖父项目_ci_07

从图中可以很明显的看出, 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>

如上配置之后效果如下:

Java Maven 子项目使用jar 包里依赖版本 maven子项目依赖父项目_ci_08

通过 dependencyManagement 标签,我们实现了最终的目的

子工程中相同的模块抽取到父工程的 dependencies 标签中,让每个子工程可以继承

子工程中不同的部分声明在父工程的 dependencyManagement 中,该标签中的依赖不会被父工程引用,但是使用了该标签可以对子模块中不同的依赖进行版本管理,例如:父工程中切换 log4j 和 commons-logging 的版本之后,如果子工程没有显示的声明版本,那么会随着父工程中版本的切换而同步切换

这里面还有一个 pluginManagement

 

二、聚合

聚合:将多个子项目添加到一个父项目中,然后通过对父项目进行操作( Maven 命令),从而实现对所有聚合的子项目的操作

例如:父项目执行 mvn package 操作,子项目也会被打包.

继承和聚合的区别

继承是告诉子项目它的父项目是谁,在哪里,聚合是告诉父项目它的子项目有哪些,分别在哪里

那么怎么实现聚合呢?

在父项目的 pom.xml 中配置

<!--子项目中定位父项目使用的是 pom.xml 来定位的
而父项目定位子项目使用的名字来定位的,为什么不使用
pom.xml 来定位呢?个人猜测可能是同一路径下有多个子
项目,使用 pom.xml 不能准确的定位到是哪个子项目-->
<modules>
	<module>../child01</module>
	<module>../child02</module>
</modules>

测试聚合后的效果

Java Maven 子项目使用jar 包里依赖版本 maven子项目依赖父项目_ci_09

  

三、依赖关系

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>

查看依赖关系

Java Maven 子项目使用jar 包里依赖版本 maven子项目依赖父项目_xml_10

从上图中可以看到,实现了依赖传递

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>

查看依赖的传递性

Java Maven 子项目使用jar 包里依赖版本 maven子项目依赖父项目_ci_11

3、依赖传递的原则

使用 Maven 不会出现 jar 包冲突,因为其通过两个原则来保证

3.1、就近原则,依赖的 jar 包距离本项目的层级越近(路径越短),优先级越高.

Java Maven 子项目使用jar 包里依赖版本 maven子项目依赖父项目_xml_12

依赖配置

// 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>

查看结果

Java Maven 子项目使用jar 包里依赖版本 maven子项目依赖父项目_jar_13

2、优先声明原则,在同一个 pom.xml 的 dependecy 标签里面, jar 包写在上面,优先级就越高.

Java Maven 子项目使用jar 包里依赖版本 maven子项目依赖父项目_ci_14

依赖配置

// 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>

查看结果

Java Maven 子项目使用jar 包里依赖版本 maven子项目依赖父项目_xml_15

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>