【maven】 依赖树(dependency、dependencyManagement)


相关文章:
maven pom.xml详解
maven 依赖树(dependency、dependencyManagement)
使用 spring-boot-dependencies 方便管理项目依赖

1. 概述

spring boot工程示例:

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!--引入spring boot-->

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <!--定义自己父项目的基本信息-->
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>11</java.version>
    </properties>

    <!--定义子模块-->
    <modules>
        <module>mall-common</module>
        <module>mall-order</module>
    </modules>

    <!--版本号参数-->
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
    </properties>

    <!--版本号参数-->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>


    <!--定义统一的版本号,但是子模块不会直接继承-->
    <dependencyManagement>
        <dependencies>

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-commons</artifactId>
                <version>${spring-cloud.version}</version>
            </dependency>

        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

1.1 父pom中的dependencies

dependencies 下引用的maven 依赖,默认被其子模块全部依赖,其子模块不用显示引用

优点:所有的子模块,不用再次引用,公用父模块的引用和版本号管理

缺点:子模块引用冗余,对于自己不需要引用的包,也会被引用过来

1.2 父pom中的dependencyManagement

父pom通过dependencyManagement管理,引用相关maven依赖后,子模块需要再次引用,但是不需要设置版本号。版本号由父pom中的dependencyManagement 引用的依赖及版本号统一管理

若子模块中,想单独升级某一个依赖的版本号,则可在子模块中针对某个依赖添加版本号,此时子模块下的那个依赖的版本号会覆盖父pom对该依赖设置的版本号

优点:父pom 统一管理各个依赖的版本号,子模块可以按需要引用自己需要的依赖,灵活设置自己需要的依赖版本号或直接依赖父pom的版本号

缺点:子模块pom中要显示引用自己需要的依赖

2. 查看依赖树

CMD命令行下,进入pom.xml所在的目录,可以是父模块的目录,也可以是子模块的目录,区别在于前者会按子模块分级,展示所有模块的依赖树,后者只查子模块自身相关的依赖树,后者的结果是前者的一个子集。

  • 通过控制台查看:
mvn dependency:tree
  • 如果要输出到文件,找到pom文件的位置 进入命令行
mvn dependency:tree >d:/tree.txt
  • 只查看包含指定的jar包

includes表示按条件过滤,verbose表示显示详细信息,方便处理冲突

mvn dependency:tree -Dverbose -Dincludes=groupId[:artifactId][:version]

括号中的参数是可选项

mvn dependency:tree -Dverbose -Dincludes=org.springframework:spring-tx

3. 处理冲突

3.1 处理冲突的原则

简介:处理jar包依赖冲突,首先,对于多个jar包都引用同一jar包的情况,最好是在程序中显式定义被共同引用的jar包的依赖,来统一版本号,方便维护

如果A和B都依赖同一jar包C,可能会出现两种情况

1.A和B引用的C版本相同

这时按照pom定义顺序选择第一个即可,没有冲突问题,如果在项目的maven中显示定义了C依赖,那么用选择项目定义的依赖,反正version都一样,没有影响

2.A和B依赖的C版本不同

选择版本高的那个,这时会出现两种结果

(1) 高版本兼容低版本,所以不会出现问题

(2)高版本不兼容低版本,假如A依赖C2版本,B依赖C3版本,C3不兼容C2,maven选择了高版本C3,对A来说会出现问题

有3种解决方法

[1]提升A版本,找到依赖C3的A版本

[2]如果B版本也可依赖C2,在项目的maven中显示定义对C2的依赖,这样所有都使用C2版本

[3]如果B版本不支持C2版本,只能降低B版本,找到依赖C2的B版本

从功能性和可维护性考虑,高版本提供的功能更多,bug更少,优先考虑1,再考虑2,最后考虑3

3.2 如何处理冲突

3.2.1、maven自动解决依赖冲突的规则是什么?

第一原则:路径最近者优先
项目A有如下的依赖关系:

A->B->C->X(1.0)

A->D->X(2.0)

则该例子中,X的版本是2.0

####第二原则:路径相等,先声明者优先

项目A有如下的依赖关系:

A->B->Y(1.0)

A->C->Y(2.0)

若pom文件中B的依赖坐标先于C进行声明,则最终Y的版本为1.0

3.2.2 如何从依赖树中找到自己预期的版本,是被哪个jar给覆盖了?

先来看个最简单的例子

例1:仅涉及单个工程或模块

slf4j-log4j12仅依赖slf4j-api这一唯一的包,前者我们定义1.7.22,但是我们故意把后者单独引入一个1.7.25的高版本

  <!--slf4j-log4j12 ,slf4j-api 均是直接引入-->
  		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.22</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.25</version>
		</dependency>

下面是依赖树日志:

[INFO] +- org.slf4j:slf4j-log4j12:jar:1.7.22:compile
[INFO] |  +- (org.slf4j:slf4j-api:jar:1.7.22:compile - omitted for conflict with 1.7.25)
[INFO] |  \- log4j:log4j:jar:1.2.17:compile
[INFO] \- org.slf4j:slf4j-api:jar:1.7.25:compile
  • compile 当前包需要编译入项目包中,和版本冲突无关。

    compile 意思相反的provided,表示当前包不需要编译入项目包中,也就是说该包将会以其他方式引入,比如tomcat的lib公共库已经提供了此包引用,此处不需以compile形式再次引入。

  • omitted for conflict with 1.7.25 表示默认依赖的是1.7.22,并与其他版本产生了冲突1.7.25(实际使用的版本),丢弃当前1.7.22。
    此时会根据前文讲述的冲突规则,最近规则,优先加载1.7.25

omitted for conflict一般是指存在2个版本有冲突,至少出现一次直接引用(个人总结的,也不一定对,比如如果没有一个直接引入的,但是有2个间接引入的,会有该关键词出现吗?)。这个例子slf4j-log4j12和slf4j-api都是直接引入的。

eclipse插件也能方便的帮助我们查看依赖树,切换pom.xml至Dependency Hierachy标签页
在这里插入图片描述
扩展知识,怎么知道slf4j-log4j12会有个默认依赖的slf4j-api?
原因是每个jar自身也是有pom.xml的,我们查看本地仓库的slf4j-log4j12
例如 D:\maven\repo\org\slf4j\slf4j-log4j12\1.7.22\slf4j-log4j12-1.7.22.pom,里面有如下配置:

 <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <type>test-jar</type>
      <version>${project.version}</version>
      <scope>test</scope>
    </dependency>

现在一目了然,一层层的依赖就是这么来的。

例2:涉及父子模块
在查看依赖树之前我们先来了解一下dependencyManagement的用法。

在包含父子项目中,允许在顶层的POM文件中,定义dependencyManagement元素。通过它元素来管理jar包的版本,让子项目中引用一个依赖而不用显示的列出版本号。Maven会沿着父子层次向上走,直到找到一个拥有dependencyManagement元素的项目,然后它就会使用在这个dependencyManagement元素中指定的版本号。

在父模块中定义了org.slf4j为高版本1.7.25,子模块中定义了slf4j-log4j12为低版本1.7.22

myparent/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>my</groupId>
	<artifactId>myParent</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<!--父模块类型为pom  -->
	<packaging>pom</packaging>
	<modules>
	<!--定义子模块  -->
		<module>mychild</module>
	</modules>
	<dependencyManagement>
	<!-- 这样做的好处:统一管理项目的版本号,确保应用的各个项目的依赖和版本一致 -->
		<dependencies>
			<dependency>
				<groupId>org.slf4j</groupId>
				<artifactId>slf4j-api</artifactId>
				<version>1.7.25</version>
			</dependency>
		</dependencies>
	</dependencyManagement>

mychild/pom.xml

  <parent>
    <groupId>my</groupId>
    <artifactId>myParent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <groupId>mychild</groupId>
  <artifactId>mychild</artifactId>
  
  <dependencies>
<!--直接引入slf4j-log4j12,但是间接引入slf4j-api-->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.22</version>
		</dependency>
	</dependencies>

依赖树

 \- org.slf4j:slf4j-log4j12:jar:1.7.22:compile
    +- org.slf4j:slf4j-api:jar:1.7.25:compile (version managed from 1.7.22)
    \- log4j:log4j:jar:1.2.17:compile
  • version managed from 1.7.22 表示最终会1.7.25版本,version managed表明最终版本的确认过程涉及<dependencyManagement></dependencyManagement>中声明的元素。

与例1相比,这里只有slf4j-log4j12作直接引入,slf4j-api是间接引入。

dependencyManagement里只是声明依赖,并不实现引入,因此子项目需要显示的声明需要用的依赖。如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom

例3
本例基于例2,注意把slf4j-api也改为直接引入
mychild/pom.xml

	<dependencies>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.22</version>
		</dependency>
		<!--与例2 相比,新增了直接引入slf4j-api-->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.22</version>
		</dependency>
	</dependencies>
[INFO] +- org.slf4j:slf4j-log4j12:jar:1.7.22:compile
[INFO] |  +- (org.slf4j:slf4j-api:jar:1.7.25:compile - version managed from 1.7.22; omitted for conflict with 1.7.22)
[INFO] |  \- log4j:log4j:jar:1.2.17:compile
[INFO] \- org.slf4j:slf4j-api:jar:1.7.22:compile
  • version managed from 1.7.22; omitted for conflict with 1.7.22
    表示使用1.7.25,不用1.7.22,version managed表明最终版本的确认过程涉及<dependencyManagement></dependencyManagement>中声明的元素;
    omitted for conflict表示直接引入的元素有冲突(个人总结的,也不一定对,比如如果没有一个直接引入的,但是有2个间接引入的,会有该关键词出现吗?)

例4
本例基于例2,额外间接引用slf4j-api,总共有2次额外引用
mychild/pom.xml

	<!-- 间接引用slf4j-api -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.22</version>
		</dependency>
	
		<!-- 新增间接引用slf4j-api -->
		<dependency>
			<artifactId>kafka-clients</artifactId>
          <groupId>org.apache.kafka</groupId>
			<version>2.1.1</version>
		</dependency>

依赖树:

 +- org.slf4j:slf4j-log4j12:jar:1.7.22:compile
 |  +- org.slf4j:slf4j-api:jar:1.7.25:compile (version managed from 1.7.22)
 |  \- log4j:log4j:jar:1.2.17:compile
 \- org.apache.kafka:kafka-clients:jar:2.1.1:compile
    +- com.github.luben:zstd-jni:jar:1.3.7-1:compile
    +- org.lz4:lz4-java:jar:1.5.0:compile
    +- org.xerial.snappy:snappy-java:jar:1.1.7.2:compile
    \- (org.slf4j:slf4j-api:jar:1.7.25:compile - version managed from 1.7.22; omitted for duplicate)
  • version managed from 1.7.22;omitted for duplicate 使用1.7.25
    omitted for duplicate 表明 存在重复的引用

参考文章《maven冲突》

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值