3. maven依赖传递

1. 依赖范围

1.1 classpath & 依赖范围

  • 上一篇博客提到,在pom文件中引用其他依赖时,可以指定依赖的范围
  • 例如,test范围只对测试代码有效,对编译或运行主代码无效
  • 这里maven在编译、运行和测试时,会使用三套不同的classpath:编译classpath、运行classpath、测试classpath
    • 编译时,Maven 会将与编译相关的依赖引入到编译 classpath 中;
    • 测试时,Maven 会将与测试相关的的依赖引入到测试 classpath 中;
    • 运行时,Maven 会将与运行相关的依赖引入到运行 classpath 中
  • 依赖范围,就是用来控制引入的依赖与这三种classpath之间的关系 。

1.2 maven的中依赖范围

compile

  • 编译依赖范围,若不明确指定scope,默认使用该值。

  • compile范围的依赖,对编译classpath、运行classpath和测试classpath都有效。如,我自己喜欢使用的commons-lang依赖

    <dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
        <version>2.6</version>
    </dependency>
    

test

  • 测试依赖范围,只对测试classpath有效,如,我们单元测试时使用的junit

provied

  • 已提供依赖范围,对编译classpath和测试classpath有效,对运行classpath无效。
  • sevlet-api,它在容器中已经提供,运行项目时就不需要该依赖了,但编译和测试时仍然需要该依赖。因此,需要将其申明为provided依赖

runtime

  • 运行时依赖,对运行classpath和测试classpath有效,对编译classpath无效
  • 如,使用JDBC驱动,编译时使用的是jdk提供的JDBC接口,只有在测试和运行时才会使用实现了JDBC接口的具体驱动。
  • 执行测试,就是在运行测试代码。因此,从这个角度可以认为,测试是一种特殊的运行

system

  • 系统依赖,同三种classpath的关系与provided一致

  • 但是,system依赖需要通过systemPath元素显式指定依赖文件的路径,是maven仓库无法解析的

  • 这些依赖往往与本机系统绑定,可能会造成项目的不可移植

    <dependency>
        <groupId>javax.sql</groupId>
        <artifactId>jdbc-stdext</artifactId>
        <version>2.0</version>
        <scope>system</scope>
        <systemPath>${java.home}/lib/rt.jar</systemPath>
    </dependency>
    

import(maven 2.09及以上)

  • 导入依赖,不会对三种classpath产生影响。《maven实战》书中,说后续会讲到该依赖。

1.3 依赖范围与classpath的总结

  • 总结起来,maven中几种依赖范围与三种classpath的关系如下:

    依赖范围编译classpath测试classpath运行classpath例子
    compileYesYesYescommons-lang
    testNoYesNojunit
    providedYesYesNoservlet-api
    runtimeNoYesYesJDBC-connector
    systemYesYesNo本地的,maven仓库之外的类库文件

2. 依赖的传递

2.1 絮絮叨叨

  • 自己本科就读的学校,比较喜欢在暑期开展学生实践培训活动。
  • 当时就读的软件工程专业,从大一到大三的暑假,都会待学生到某个类似培训学校的地方,开展某种编程技术的培训。
  • 大一,好像是基于MFC的编程;大二,好像是基于C#的编程;大三,是基于Android的编程。
  • 自己清楚地记得进行Andriod编程时,培训老师说:你们用U盘把这些jar拷贝到自己的电脑上,不然不然你们自己去找jar几乎都不可能成功的 😂
  • 现在,学习了maven的相关知识后,自己也理解为什么了。
  • 我相信很多人和我一样:
    ① 创建java项目时,很多时候只是简单的创建Java项目
    ② 创建一个lib包,需要使用到的依赖A,则去网上搜索,然后下载、添加并Add as library很多时候,下载的jar很可能版本不对,还需要重新下载。
    ③ 很多时候,程序运行时会提示找不到某个类,这时候又开始重复步骤②
    ④ 最后,下载了N个jar后,程序终于能运行起来了。
  • 如果我们创建Java项目时,使用的是maven项目就不会出现这样的问题了。
  • 因为,maven能帮助我们解析直接依赖以及传递性依赖导致的间接依赖

2.2 依赖的传递性

  • 假设,我们在pom文件中引用了依赖A(scope为compile),而依赖A的pom文件中又医用了依赖B(scope为compile)。这样,我们的项目通过传递性依赖,间接引用了依赖B。
    在这里插入图片描述
  • 一般地,我们称项目对于依赖A是第一直接依赖,称依赖A对于依赖B是第二直接依赖,称项目对于依赖B是传递性依赖
  • 从前面可知,引入依赖时存在scope,第一直接依赖和第二直接依赖的scope直接决定了传递性依赖的scope。
  • 下表总结了第一直接依赖和第二直接依赖的scope对传递性依赖scope的影响:
  1. 表格的第一列,表示第一直接依赖的scope

  2. 表格的第一行,表示第二直接依赖的scope

  3. 行列相交的格子,表示在第一直接依赖和第二直接依赖scope的影响下,传递性依赖的scope。

  4. 例如,当第一直接依赖的scope为test,第二直接依赖的scope为compile时,传递性依赖的scope为test

  5. ——表示依赖不传递

    /compiletestprovidedruntime
    compilecompile————runtime
    testtest————test
    providedprovided——providedprovided
    runtimeruntime————runtime
  • 从表格可以总结出如下规律:
  1. 第二直接依赖的scope为compile时,传递性依赖的scope与第一直接依赖的scope一致
  2. 第二直接依赖的scope为test时,无论第一直接依赖的scope为何值,依赖都不会传递
  3. 第二直接依的scope为provided时,只有第一直接依赖的scope也是provided时,传递才会传递,并且传递性依赖的scope也为provided
  4. 第二直接依赖的scope为runtime时,传递性依赖的scope与第一直接依赖的scope一致,但compile除外。若第一直接依赖的scope为compile,则传递性依赖的scope为runtime
  • 总结: maven的依赖传递性,帮我们大大地简化了依赖的声明,使编程人员只需要关注直接依赖,而无需过多关注传递性依赖。

2.3 依赖调解(两个原则)

  • 依赖的使用中,经常会遇到这样的情况:A → B → C → X(version为1.20),A → D → X(verison为1.25)。
  • 在maven的依赖解析中,是坚决不允许同一依赖存在两个不同verison的引用的,即不允许依赖重复
  • 遇到上述情况,maven在进行依赖解析时,会选择 路径最近者优先的原则,只引入1.25版本的依赖X。 —— 我喜欢叫做,最短路径优先 😂
  • 除了上述情况,这样的情况也有可能存在:A → B → X(version为1.20),A → C → X(verison为1.25)。
  • 从maven2.0.9开始,针对上述情况将采用第一声明者优先原则,会引入先声明的1.20版本的依赖X。 —— 我喜欢叫做,FIFO 😂

2.4 可选依赖

  • 上一篇博客,我们还提到了<optional>标签,它用于标志依赖是否可选。

  • 其实,我并未使用过或者遇到过 😂

  • 书中举了这样一种情况:A → B → X(可选),A → C → Y(可选)
    ① 项目B同时实现了两种特性,分别依赖X和Y。
    ② 这两种特性是互斥的,用户不可能同时使用这两种特性
    ③ 例如,实现了访问MySQL数据库和PostgreSQL的依赖B,其pom文件声明如下:

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.48</version>
        <optional>true</optional>
    </dependency>
    
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <version>42.2.5</version>
        <optional>true</optional>
    </dependency>
    
  • 可选依赖X和Y只会对依赖B产生影响,当项目A依赖B时,依赖不会被传递
    在这里插入图片描述

  • 这时,用户需要根据需要,在项目A的pom文件中,显式地引入依赖X和依赖Y。

  • 注意: 一般,不要使用<optional>定义可选依赖,项目需要同时支持两个互斥的、可选的特性时,可以创建两个不同的模块。

3. 依赖的一些最佳实践

3.1 排除依赖

  • 排除不稳定的传递性依赖: A → B → X(snapshot版本)
    ① 传递性依赖X是不稳定的snapshot版本,其不稳定性很可能会影响当前项目
    ② 需要通过<exclusions>标签,从依赖B中去除不稳定的依赖X
    ③ 然后,在项目A的pom文件中显式声明稳定版本的依赖X
  • 排除不在maven中央仓库中的传递性依赖:A → B → X
    ① 依赖X由于版权原因,在maven中央仓库中是不存在的,即maven无法解析传递性依赖X
    ② 需要通过<exclusions>标签,从依赖B中去除不存在的依赖X
    ③ 找到依赖X的替代依赖——依赖Y,在项目A的pom文件中显式声明依赖Y

3.2 依赖归类(版本声明)

  • 在Java编程中,我们会经常定义一些常量

    public static final String HTTP_HEAD = "http://";
    
  • 定义常量的目的是,可能很多地方都会使用到该常量的值,如果我们需要将http改为https,只需要修改常量的定义即可,而无需逐个修改值。

  • 尤其是在进行spring编程时,我们会使用同一version的spring-corespring-context等依赖

  • 这时,我们可以在pom文件<properties>中定义version,方便统一管理依赖的version

  • kafka-client的version为例:

    <properties>
        <kafka.versoin>2.3.1</kafka.versoin>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>${kafka.versoin}</version>
        </dependency>
    </dependencies>
    

3.3 依赖的优化

3.3.1 已解析依赖

  • maven会自动帮我们解析直接依赖和传递性依赖,能根据规则判断每个依赖的范围 (依赖的传递性),能根据两个原则 (最短路径、FIFO) 对依赖冲突进行调节等。
  • 我们将通过maven解析后,最终得到的项目的所有依赖称为已解析依赖
  • 使用如下命令,可以查看项目已解析依赖:
    mvn dependency:list
    

3.3.2 依赖树

  • 一般地,我们将项目的直接依赖称为顶层依赖,将顶层依赖的依赖,称为第二层依赖。以此类推,还有第三层、第四层依赖等。

  • 通过如下命令,可以查看项目已解析依赖的树形结构:

    mvn dependency:tree
    
  • 自己某个项目的依赖树实例:

    [INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ test ---
    [INFO] org.example:test:jar:1.0-SNAPSHOT
    [INFO] +- org.apache.kafka:kafka-clients:jar:2.3.1:compile
    [INFO] |  +- com.github.luben:zstd-jni:jar:1.4.0-1:compile
    [INFO] |  +- org.lz4:lz4-java:jar:1.6.0:compile
    [INFO] |  +- org.xerial.snappy:snappy-java:jar:1.1.7.3:compile
    [INFO] |  \- org.slf4j:slf4j-api:jar:1.7.26:compile
    [INFO] +- org.slf4j:slf4j-jdk14:jar:1.7.25:compile
    [INFO] +- commons-lang:commons-lang:jar:2.6:compile
    [INFO] +- joda-time:joda-time:jar:2.10.5:compile
    [INFO] +- com.facebook.airlift:json:jar:0.194:compile
    [INFO] |  +- com.google.inject:guice:jar:4.2.2:compile
    [INFO] |  |  \- aopalliance:aopalliance:jar:1.0:compile
    [INFO] |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.9.8:compile
    [INFO] |  +- com.fasterxml.jackson.core:jackson-core:jar:2.9.8:compile
    [INFO] |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.9.8:compile
    [INFO] |  +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.9.8:compile
    [INFO] |  +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.9.8:compile
    [INFO] |  +- com.fasterxml.jackson.datatype:jackson-datatype-guava:jar:2.9.8:compile
    [INFO] |  +- com.fasterxml.jackson.datatype:jackson-datatype-joda:jar:2.9.8:compile
    [INFO] |  +- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.9.8:compile
    [INFO] |  +- javax.inject:javax.inject:jar:1:compile
    [INFO] |  \- com.google.guava:guava:jar:26.0-jre:compile
    [INFO] |     +- com.google.code.findbugs:jsr305:jar:3.0.2:compile
    [INFO] |     +- org.checkerframework:checker-qual:jar:2.5.2:compile
    [INFO] |     +- com.google.errorprone:error_prone_annotations:jar:2.1.3:compile
    [INFO] |     +- com.google.j2objc:j2objc-annotations:jar:1.1:compile
    [INFO] |     \- org.codehaus.mojo:animal-sniffer-annotations:jar:1.14:compile
    [INFO] \- io.airlift:units:jar:1.3:compile
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    

3.3.3 未声明或未使用的依赖

  • 如果参与社区项目的二次开发,你会发现社区项目对依赖有着严格管理,禁止出现used undeclared dependenciesunuesd declared dependencies
  • 这时,可以借助以下命令对项目的当前依赖进行分析,已了解为声明或未使用的依赖:
mvn dependency:analyze
  • 结果实例:

    [INFO] --- maven-dependency-plugin:2.8:analyze (default-cli) @ test ---
    [WARNING] Used undeclared dependencies found:
    [WARNING]    com.fasterxml.jackson.core:jackson-annotations:jar:2.9.8:compile
    [WARNING]    com.google.guava:guava:jar:26.0-jre:compile
    [WARNING]    com.fasterxml.jackson.core:jackson-databind:jar:2.9.8:compile
    [WARNING]    org.slf4j:slf4j-api:jar:1.7.26:compile
    [WARNING] Unused declared dependencies found:
    [WARNING]    org.slf4j:slf4j-jdk14:jar:1.7.25:compile
    [WARNING]    joda-time:joda-time:jar:2.10.5:compile
    [WARNING]    com.facebook.airlift:json:jar:0.194:compile
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    
① 未声明的依赖
  • 从分析中可以看出,有些依赖我并未显式声明就使用了。例如,依赖com.fasterxml.jackson.core:jackson-annotations
  • 为何我的程序还能正常运行呢?那是因为,com.facebook.airlift:json中声明了该依赖,使得com.fasterxml.jackson.core:jackson-annotations作为传递性依赖被引入了。
  • 但是,通过A → B → X的方式,引入依赖X是具有潜在风险的。例如,若依赖B升级,可能会导致依赖X升级。这时,依赖X的接口可能很可能就变化了,之前可以使用的方法func(),现在就无法再o使用了。
  • 总结: 对于未声明就使用的依赖,我们应该在项目中显式声明,而不是传递性依赖去引入。
② 未使用的依赖
  • 上面的分析结果中,org.slf4j:slf4j-jdk14是一个已声明、但未使用的依赖。但是,我想使用org.apache.kafka:kafka-clients就必须显式声明该依赖,否则程序无法运行。
  • joda-time:joda-time是一个我已经显式声明了,但并未使用的依赖。我可以直接删除该依赖,对项目的运行无任何影响。
  • 总结: mvn dependency:analyze命令,只能分析项目主代码和测试代码编译时需要使用到的依赖,一些测试或运行时需要的依赖它无法检测到。对于未使用的依赖,我们不能一味的删除,而是需要通过分析后再谨慎删除。

4. 总结

  • 依赖范围与三种classpath的关系
  • 传递性依赖的概念,以及第一直接依赖、第二直接依赖的scope对传递性依赖scope的影响
  • maven中如何保证依赖不重复的 —— 依赖调解(两个原则)
  • 可选依赖:<optional>true,依赖无法传递,需要显式声明;通过将一个模块拆为多个模块,解决可选依赖
  • 依赖的一些最佳实践:
    ① 去除不稳定的/maven中央仓库无法支持的依赖
    ② 依赖的归类:像声明Java常量一样,声明依赖的版本号
    ③ 依赖的优化:三种命令 —— 查看已解析依赖、以树形方式查看已解析依赖、分析依赖(未声明/未使用)
  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值