Maven中依赖中的scope的作用

Maven中依赖中的scope的作用
背景:通过网上的视频课程学习了Maven软件工具,在老师讲到scope的作用时,产生了一些疑惑(会在下文中指出).。于是,网上查找答案,结果,不尽人意。有些是内容描述不准确,或者不全面,例如没有介绍新添加的import。有些给出来官方文档,但是却没有全部解释,或者翻译存在问题。 找了好多文章,几乎没有一个是完全没有问题的,而且他们的浏览量是很高。因此,想要写一篇文章更全面、正确的解释scope的作用博文供大家参考。(注:本文基于)。

一 、来自网上的解释

1. 某视频课程中老师的讲解

www.zeeklog.com  - Maven中依赖中的scope的作用

2. 技术博客文章中的观点

  • compile: 默认值,编译、运行、测试都需要或者有效,会被打包
  • test: 仅仅参与测试,测试代码的编译和执行需要。另一种说法,仅测试有效 不会被打包
  • runtime: 测试, 运行需要参与,不参与项目的编译,会被打包
  • provided: 编译、测试都需要。运行存在争议,有说需要的,有说不需要的。容器中会提供,不会被打包
  • system: 与provided相同,依赖不在仓库,在本地, 需要systemPath属性
  • import: 导入的范围,它只在使用dependencyManagement中,表示从其他pom中导入dependecy的配置 (因为是后来添加的,只有很少一部分博文介绍了)

3. 以上说法中存在的问题

需要先介绍下default生命周期的阶段:

  • validate 验证项目是正确的,所有必要的信息都是可用的
  • compile 编译项目源代码
  • test 使用单元测试框架测试编译后的源代码
  • package 获取已编译的代码,并将其打包为可发行的格式,例如JAR。
  • verify获取已编译的代码,并将其打包为可发行的格式,例如JAR。
  • install将包安装到本地仓库,供本地项目使用
  • deploy 将包发布到远程仓库(remote repository),方便其他开发人员和项目共享。

问题1:编译、测试(这里提及的编译、测试,更准确的说法是编译阶段和测试阶段,因为测试代码也存在编译,所以为了不引起歧义,使用术语编译阶段和测试阶段更好)在default生命周期阶段都有对应,但是却不包含运行,有些人把运行也作为了生命周期的一部分。

问题2:<scope>compile</scope> 在编译、测试时需要(或者有效、参与),编译阶段需要是可以理解的,因为我们的源代码使用了依赖。在测试阶段需要该如何理解呢?容易让人觉得测试源代码也使用了依赖,其实不是这样的。在测试阶段,首先会编译测试代码,然后运行测试代码,测试代码会调用主程序代码(例如,其中的方法),看方法是否能达到预期的功能。在测试代码,调用主程序代码时,主程序代码方法运行,这时会用到依赖。也就是说测试阶段用到依赖,不是测试源代码中直接依赖,而是测试代码运行主程序代码间接使用了依赖。这里需要的定义不统一。

问题3:我们使用Mavne管理项目,开发的项目不止webapp,还可以开发供其他人使用的jar工具包,也可能是桌面应用程序,依赖的包是不会打包进去的。打包的行为只是webapp特有的,说compile会打包不严谨。

二、官方文档解释

Maven 管理的项目可能是:供其他项目依赖的jar工具包(例如,Log4j)、桌面应用程序(例如、我们的IDEA、Maven也是程序)、WebApp等等。

参考官方文档:

Dependency scope is used to limit the transitivity of a dependency and
to determine when a dependency is included in a classpath.

依赖作用域用于限制依赖传递并且控制(或决定)何时把一个依赖添加到类查找路径classpath。

从这里我们可以得出依赖作用域的主要作用有两个:

  1. 限制依赖传递
  2. 控制把依赖添加到classpath

classpath 类查找路径会在javac命令中使用,指出源码依赖的class文件的位置,以便可以正常编译源码。另外,会在java命令中使用,指出编译好的程序使用的class文件的位置,以便可以正常运行。

因此classpath在四中情况下会被使用:主程序代码的编译、测试程序代码的编译、主程序代码的运行,测试程序代码的运行。

到目前为止,maven version 3.6,共有六种scope :

compile
默认值,当不指定依赖的scope时,使用该值。依赖在项目中的所有classpath(主程序、测试程序代码的编译和运行)中都可以使用。此外,依赖会传递到依赖的项目。

provided
和compile很像,但是它表示你期待JDK或者容器在运行时提供了此依赖。例如,当构建一个JaveEE 的webapp时,会设置Servlet API和相关的JaveEE APIs的scope为provided,因为web容器提供了这些类。依赖会被添加到编译阶段和测试阶段的classpath(编译阶段进行主程序代码的编译,测试阶段进行测试代码的编译和运行),但是不会添加到运行时的classpath(这里的运行仅仅指主程序代码的运行,不涉及测试阶段的运行)。它不会被传递。

runtime
编译不需要,但是运行(这里的运行包括主主程序代码和测试程序代码的运行)需要。依赖会被添加到运行时和测试阶段的classpath,不会添加到编译的classpath.

test
正常使用应用程序并不需要的依赖项,仅仅用于测试阶段(包括测试代码的编译和运行)。不会被传递。这个scope值通常用于测试的类库,例如JUnit和Mockito。它还用于非测试库,如Apache Commons IO,如果这些库在单元测试中使用(src/test/java),而不在模型代码中使用(src/main/java)。

system
与provided类似,只是jar在本地系统,不在仓库。

import
这个作用域仅支持部分中类型为pom的依赖项。 它表示该依赖项将被替换为指定POM的部分中的有效依赖项列表。 由于它们被替换了,具有import作用域的依赖项实际上并不参与限制依赖项的传递性。

下面介绍scope如何影响(限制)依赖的传递性。我的项目依赖A,A又依赖B,那么A就是我项目的直接依赖,B就是我项目的间接依赖。以第一行为例,我项目的直接依赖的A的scope为compile,间接依赖B的scope为compile,可以看到交集为compile(*),那么传递依赖的scope最终为compile;如果间接依赖B的scope为provided,交集为 ”-“,则表示项目不需要该依赖;等等,不一一介绍了。
总之,从图中可以看出,如果间接依赖为provided或者test,则该项目不需要此依赖,如果间接依赖是compile,则最终间接依赖的scope和直接依赖的scope相同;如果间接依赖为runtime,则除了直接依赖scope为compile,间接依赖最终为runtime,这一点比较特殊为,其他情况,也是和直接依赖相同。

www.zeeklog.com  - Maven中依赖中的scope的作用

(*)的说明,可以看到直接依赖的scope为compile,其实,在编译阶段是不需要传递依赖,传递依赖的scope应为runtime。但是在推断规则里把传递依赖的scope设置为compile,其主要目的是显示的列出该项目源码程序所使用的所有依赖而已。

另外,直接依赖和间接依赖是相对而言的,例如我项目依赖A,A依赖B,B依赖C。那么对于我的项目而言A是直接依赖,B是间接依赖。对于依赖A(它本身也是一个项目),它的直接依赖是B,间接依赖是C。所以,C在我项目中的是否被依赖,以及依赖的scope为何,需要先确定C在A中最终的依赖,然后确定C在我项目中依赖情况。

A(compile) --> B(runtime) --> C(compile)

从前往后决定依赖,具有“短路效应”,先确定依赖B的scope,根据上表,推断出,依赖B的scope为runtime,然后在根据B的Scope推断C的依赖的scope为runtime。

A(compile) --> B(provided) --> C(compile)

依赖B的scope推断为“-”,即不需要依赖B,进而B的所有依赖也不需要了。