包皮

V1

2022/05/08阅读:24主题:Pornhub黄

Maven依赖范围详解

引言

Java中编译代码、运行代码都需要用到classpath变量,classpath用来列出当前项目需要依赖的jar包所在路径,关于classpath和jar,可阅读文章classpath和jar

maven中用到classpath的地方有:

  • 编译源码
  • 编译测试代码
  • 运行测试代码
  • 运行项目

每个地方需要的classpath对应的值可能是不一样的,比如:

  • 项目中可能需要用junit来写一些测试用例,此时需要引入junit的jar包,但是项目上线之后就用不到了,所以运行环境中是不需要的
  • 项目中用到了servlet相关的jar包,但是部署的时候,我们将其部署在tomcat中,而tomcat中自带了servlet的jar包,上线之后servlet相关的jar由web容器提供,也就是说打包的时候,不需要将servlet相关的jar打入war包了
  • 像jdbc的驱动,只有在运行的时候才需要,编译的时候是不需要的

针对这些需求,maven中的scope为我们提供了支持,scope专门用来控制被依赖的构件与classpath的关系。

一、Maven 依赖范围 scope

scope有以下6种取值:

参考官方文档 Dependency Scope

1. compile

  • 编译依赖范围
  • 默认值
  • 编译、编译测试、运行测试、运行 4种情况都有效

2. provided

  • 已提供依赖范围。表示项目的运行环境中已经提供了所需要的构件。
  • 编译、编译测试、运行测试 3种情况都有效,但在运行时无效。
  • 比如上面说到的servlet-api。

3. system

  • 系统依赖范围
  • 和provided依赖范围完全一致
  • 不同的是,使用system范围的依赖时必须通过systemPath元素显式指定依赖文件的路径。这种依赖直接依赖于本地路径中的构件,可能每个开发者机器中构件的路径不一致。如果使用这种写法,你的机器中可能没有问题,但在别人的机器中就会有问题,建议谨慎使用。
  • 如下:
<dependency>
    <groupId>com.javacode</groupId>
    <artifactId>rt</artifactId>
    <version>1.8</version>
    <scope>system</scope>
    <systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>

4. runtime

  • 运行时依赖范围
  • 编译测试、运行测试、运行 3种情况都有效,但在编译时无效。
  • 比如上面说到的jdbc驱动

5. test

  • 测试依赖范围
  • 只对 编译测试、运行测试 2种情况有效
  • 比如junit,它只有在编译测试代码及运行测试的时候才需要。

6. import

  • 这个比较特殊,后面的文章中单独讲
  • This scope is only supported on a dependency of type pom in the dependencyManagement section
  • springboot和springcloud中用到的比较多。

以上的前5个scope取值和生效范围,用一张图来表示更清晰一些:

二、依赖的传递

1、scope取值对依赖传递的影响

假设A依赖于B,B依赖于C,我们说A对于B是第一直接依赖,B对于C是第二直接依赖,而A对于C是传递性依赖,而第一直接依赖的scope和第二直接依赖的scope决定了传递依赖的范围,即决定了A对于C的scope的值。

下面我们用表格来列一下这种依赖的效果,表格最左边一列表示第一直接依赖(即A->B的scope的值),而表格中的第一行表示第二直接依赖(即B->C的scope的值),行列交叉的值显示的是A对于C最后产生的依赖效果。

compile provided test runtime
compile compile - - runtime
provided provided - - provided
test test - - test
runtime runtime - - runtime

解释一下

1、比如A->B的scope是compile,而B->C的scope是test,那么按照上面表格中,对应第2行第4列的值-,那么A对于C是没有依赖的,A对C的依赖没有从B->C传递过来,所以A中是无法使用C的

2、比如A->B的scope是compile,而B->C的scope是runtime,那么按照上面表格中,对应第2行第5列的值为runtime,那么A对于C是的依赖范围是runtime,表示A只有在运行的时候C才会被添加到A的classpath中,即对A进行运行打包的时候,C会被打包到A的包中

3、仔细看一下,从列的方向看,上面的表格是有规律的:

  • 当B->C依赖是compile的时候(表中第2列),那么A->C的依赖范围和A->B的sope是一样的;
  • 当B->C的依赖是provided和test的时候(表第3、4列),那么B->C的依赖无法传递给A;
  • 当B->C的依赖是runtime的时候(表第5列),分2种情况,
    • A->B的sope是compile时,那么C按照B->C的scope传递给A
    • A->B的sope是其它值时,那么C按照A->B的scope传递给A

2、依赖传递2原则

现实中可能存在这样的情况:A->B->C->Y(1.0),A->D->Y(2.0),此时Y出现了2个版本,1.0和2.0,此时maven会选择Y的哪个版本?

解决这种问题,maven有2个原则:

  • 路径最近原则

上面A->B->C->Y(1.0),A->D->Y(2.0),Y的2.0版本距离A更近一些,所以maven会选择2.0。

但是如果出现了路径是一样的,如:A->B->Y(1.0),A->D->Y(2.0),此时maven又如何选择呢?

  • 最先声明原则

如果出现了路径一样的,此时会看A的pom.xml中所依赖的B、D在dependencies中的位置,谁的声明在最前面,就以谁的为主,比如A->B在前面,那么最后Y会选择1.0版本。

3、可选依赖 (optional元素)

有这么一种情况:

A->B中scope:compile
B->C中scope:compile

按照上面介绍的依赖传递性,C会传递给A,被A依赖。

假如B不想让C被A自动依赖,怎么做呢?

dependency元素下面有个optional,是一个boolean值,表示是一个可选依赖,B->C时将这个值置为true,那么C不会被A自动引入。

4、排除依赖

A项目的pom.xml

<dependency>
    <groupId>com.javacode</groupId>
    <artifactId>B</artifactId>
    <version>1.0</version>
</dependency>

B项目1.0版本的pom.xml

<dependency>
    <groupId>com.javacode</groupId>
    <artifactId>C</artifactId>
    <version>1.0</version>
</dependency>

上面A->B的1.0版本,B->C的1.0版本,scope都是默认的compile,根据前面讲的依赖传递性,C会传递给A,会被A自动依赖.

但若是C此时有个新的版本2.0,A想使用2.0的版本,此时A的pom.xml中可以这么写:

<dependency>
    <groupId>com.javacode</groupId>
    <artifactId>B</artifactId>
    <version>1.0</version>
    <exclusions>
        <exclusion>
            <groupId>com.javacode</groupId>
            <artifactId>C</artifactId>
        </exclusion>
    </exclusions>
</dependency>

上面使用使用exclusions元素排除了B->C依赖的传递,也就是B->C不会被传递到A中。

exclusions中可以有多个exclusion元素,可以排除一个或者多个依赖的传递,声明exclusion时只需要写上groupId、artifactId就可以了,version可以省略。

总结

本文以不同场景下,使用不同的classpath为开始,详细介绍了Maven的依赖范围scope,以及依赖范围与依赖传递之间的关系。

参考资料

详解maven解决依赖问题

Dependency Scope

分类:

后端

标签:

Java

作者介绍

包皮
V1