
包皮
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,以及依赖范围与依赖传递之间的关系。
参考资料
作者介绍
