传递依赖
maven通过读取分析工程依赖的其他工程的pom文件,自动的把依赖工程对应的依赖(包括这些工程自身的依赖以及从父工程继承到的依赖)加入到当前工程的依赖里面。拉勾IT课小编为大家分解
传递依赖机制虽然可以让我们方便的引入项目需要的全部依赖,但很容易就会使我们工程的依赖变的庞大复杂,并且引入的依赖很可能会同时依赖一个jar包的不同版本。因此maven在传递依赖机制中加入了一些机制来管理最终加入到工程中的依赖项
依赖仲裁(Dependency mediation)
依赖范围(Dependency scope)
依赖管理(Dependency management)
排除依赖(Excluded dependencies)
选择性依赖(Optional dependencies)
依赖仲裁
当在依赖树中出现同一个依赖的多个版本时,依赖仲裁用来决定最终采用哪个版本。
maven采用选择最近的机制来决定最终的版本号,最近指的是在工程的依赖树中距离当前的工程路径最短,这就是为什么我们可以通过在当前工程中声明一个特定依赖,从而复盖传递过来的依赖的原因。如果两个依赖在依赖树中的距离一样,则选择最先声明的。
1.场景1工程A有如下依赖树
2. A
3. ├── B
4. │ └── C
5. │ └── D 2.0
6. └── E
7. └── D 1.0
8.
此时对于D的依赖,有两条路径
A -> B -> C -> D 2.0
和
A -> E -> D 1.0
,因为第二条的路径短,所以最终选择
D 1.0
9.场景2工程A有如下依赖树
10. A
11. ├── B
12. │ └── C
13. │ └── D 2.0
14. └── E
15. └—— F
16. └—— D 3.0
17.
此时对于D的依赖有两条
A -> B -> C -> D 2.0
和
A -> E -> F -> D 3.0
,此时两条路径一样长,选择先声明的,所以最终选择
D 2.0
根据依赖仲裁的机制,当我们在自己的工程中明确写明一个依赖的版本时,就可以确保这就是最终采用的版本。但是有一个例外就是硬性需求(
Hard requirements
)优先级总是高于
软需求
(
Soft requirement
)
依赖范围
1.classpath
在说明依赖范围的作用之前,先简单了解一下maven的执行环境信息。maven在执行不同命令如
compile
、
test
,
或者是在一个构建的不同phase,会利用不同的classpath对代码执行编译、测试、运行,默认预设了如下四种classpath
compile classpath
runtime classpath
test classpath
plugin classpath
其中
plugin classpath
是插件执行的path,正常应用开发中不会涉及到。另外三种path则和我们息息相关。
可以通过在
pom.xml
文件中追加如下
plugin
查看具体工程的各个classpath,
org.apache.maven.plugins
maven-antrun-plugin
1.7
compile
compile
run
2.scope依赖范围就是用来控制依赖在哪一个classpath中使用,同时限定哪些依赖可以向后传递。在maven中,依赖总共有六种scope
ocompile(编译)
compile
是默认的scope,当依赖没有明确指定scope时,maven会自动设置成
compile
。会出现在所有环境里面(测试、编译、运行)。会向后进行传递。
oprovided(提供)
provided
表示运行环境会提供这个依赖,例如servlet-api相关的依赖,因为在servelet容器中已经有了,所以在其中运行应用时就不需要这个依赖。会出现在编译、测试环境下,但是不会出现在运行环境中。不会向后传递。
oruntime(运行)
runtime
表示运行时依赖,指在编译时不需要,在运行时需要的依赖。例如数据库连接的具体实现
mysql-connector-java
。会出现在运行、测试环境下,但是不会出现在编译环境中。会向后传递。
otest(测试)
test
表示在应用正常运行时不需要这个依赖,只在编译测试代码和执行测试用例时需要。例如
JUnit
和
Mockito
相关的依赖。出现在测试环境中,不出现在编译和运行环境中。不会向后传递。
osystem(系统)
不推荐使用
system
和
provided
范围比较像,也是由运行环境提供,但是一般指用来区分不同操作系统下的依赖。可通过
systemPath
来具体指定依赖的位置.会出现在编译、测试环境下,但是不会出现在运行环境中。不会向后传递。
oimport
import
类型的依赖只能出现在
模块中,用来引入
pom
类型的工程。其效果相当于是把这个依赖用它引入的pom工程中有效的
中的依赖列表替换掉,具体例子参考
Dependency Management
。因为是用在
模块中,所以
import
依赖不会影响真正的依赖传递。
3.对于及联依赖,可以用如下表来说明
compile
provided
runtime
test
compile
compile
-
runtime
provided
provided
-
provided
runtime
runtime
-
runtime
test
test
-
test
左边一列是我们工程直接依赖对应的scope
上边一列是我们直接依赖的工程对应依赖的scope
交叉的部分是我们工程中对依赖的依赖对应的scope
只有
compile
和
runtime
可以向后传递
4.最后追加一个依赖的例子
5.
6.
7.
org.yaml
8.
snakeyaml
9.
1.17
10.
11.
12.
13.
junit
14.
junit
15.
4.12
16.
test
17.
18.
19.
20.
mysql
21.
mysql-connector-java
22.
8.0.19
23.
runtime
24.
25.
26.
27.
javax.servlet
28.
servlet-api
29.
2.3
30.
provided
31.
32.
33.
34.
对应的各个环境的path
compile classpath: /Users/chengaofeng/ideaspace/maven-learn/maven-test/target/classes:/Users/chengaofeng/.m2/repository/org/yaml/snakeyaml/1.17/snakeyaml-1.17.jar:/Users/chengaofeng/.m2/repository/javax/servlet/servlet-api/2.3/servlet-api-2.3.jar
runtime classpath: /Users/chengaofeng/ideaspace/maven-learn/maven-test/target/classes:/Users/chengaofeng/.m2/repository/org/yaml/snakeyaml/1.17/snakeyaml-1.17.jar:/Users/chengaofeng/.m2/repository/mysql/mysql-connector-java/8.0.19/mysql-connector-java-8.0.19.jar:/Users/chengaofeng/.m2/repository/com/google/protobuf/protobuf-java/3.6.1/protobuf-java-3.6.1.jar
test classpath: /Users/chengaofeng/ideaspace/maven-learn/maven-test/target/test-classes:/Users/chengaofeng/ideaspace/maven-learn/maven-test/target/classes:/Users/chengaofeng/.m2/repository/org/yaml/snakeyaml/1.17/snakeyaml-1.17.jar:/Users/chengaofeng/.m2/repository/junit/junit/4.12/junit-4.12.jar:/Users/chengaofeng/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar:/Users/chengaofeng/.m2/repository/mysql/mysql-connector-java/8.0.19/mysql-connector-java-8.0.19.jar:/Users/chengaofeng/.m2/repository/com/google/protobuf/protobuf-java/3.6.1/protobuf-java-3.6.1.jar:/Users/chengaofeng/.m2/repository/javax/servlet/servlet-api/2.3/servlet-api-2.3.jar
依赖管理
1.依赖管理的第一个用处是用来将依赖信息进行集中化管理。
当我们有许多工程继承自一个parent时,最常见的做法是把这些工程的依赖信息统一放在父pom的dependencyManagement中,在子工程中只指定依赖的group和artifactId,这样就可以保证所有工程中依赖相同的版本。当需要修改版本时,只用修改父pom里面dependencyManagement中的定义,所有子模块就自动依赖到修改后的版本。
例:
Project A:
...
group-a
artifact-a
1.0
group-c
excluded-artifact
group-a
artifact-b
1.0
bar
runtime
Project B:
...
group-c
artifact-b
1.0
war
runtime
group-a
artifact-b
1.0
bar
runtime
A工程 和B工程有一个相同的依赖
group-a:artifact-b:1.0
,
另外各自都有一个特有的依赖。如果用依赖管理,可以将依赖信息放到如下所示的父pom中
...
group-a
artifact-a
1.0
group-c
excluded-artifact
group-c
artifact-b
1.0
war
runtime
group-a
artifact-b
1.0
bar
runtime
之后A工程的pom可以变成下面的内容
...
group-a
artifact-a
group-a
artifact-b
bar
B工程的pom转变为
...
group-c
artifact-b
war
group-a
artifact-b
bar
因为从dependencyManagement中找到匹配的依赖需要四个信息{groupId, artifactId, type, classifier},其中
type
的默认值是
jar
,
classifier
的默认值是
null
,
所以在上面两个pom中,依赖的类型不是
jar
时,都额外追加了
type
。
2.依赖管理的另一个重要的作用是控制传递依赖的版本
例:
父模块Project A:
4.0.0
maven
A
pom
A
1.0
test
a
1.2
test
b
1.0
compile
test
c
1.0
compile
test
d
1.2
子模块Project B:
A
maven
1.0
4.0.0
maven
B
pom
B
1.0
test
d
1.0
test
a
1.0
runtime
test
c
runtime
对于子模块Project B,
对于a,因为直接在
dependencies
中指定了a的版本1.0,所以a的版本一定会是1.0
对于c,因为在父
dependency management
中指定了版本1.0,所以c的版本一定是1.0
对于b,如果a或c中依赖了b,因为父
dependency management
中指定了版本1.0,并且在传递依赖中,
dependency management
的优先级高于依赖仲裁,所以无论a、c中依赖的b是什么版本,b的版本一定是1.0
对于d,如果a或c中依赖了d,因为当前工程中的
dependency management
优先级高于父pom中的
dependency management
,又由于
dependency management
的优先级高于依赖仲裁,所以d的版本一定是1.0
由上可以看出,当传递依赖引入同一个依赖的不同版本时,靠依赖仲裁的最近原则,会给人带来困惑,依赖顺序会影响到最终的结果,但是引入依赖管理后,就可以明确决定依赖的具体版本了。
3.依赖导入
在讨论依赖范围时,我们简单探讨了
import
类型,用来在 依赖管理 中引入其他的pom类型的工程。下面我们通过一个例子说明如何在
dependency management
利用
import
导入其他工程定义好的 依赖管理。
o例子1:
Project A:
4.0.0
maven
A
pom
A
1.0
test
a
1.2
test
b
1.0
compile
test
c
1.0
compile
test
d
1.2
Project B:
4.0.0
maven
B
pom
B
1.0
maven
A
1.0
pom
import
test
d
1.0
test
a
1.0
runtime
test
c
runtime
A是一个pom类型的工程
B在
dependencyManagement
中通过
import
引入了
A
,其作用类似于把
A
中
dependencyManagement
中的依赖列表插入到
B
的
dependencyManagement
中。
o例子2:
Project X:
4.0.0
maven
X
pom
X
1.0
test
a
1.1
test
b
1.0
compile
Project Y:
4.0.0
maven
Y
pom
Y
1.0
test
a
1.2
test
c
1.0
compile
Project Z:
4.0.0
maven
Z
pom
Z
1.0
maven
X
1.0
pom
import
maven
Y
1.0
pom
import
X、Y中有一个相同的定义
a
在工程Z中通过
import
引入了X、Y。因为Z中没有重新定义对a的依赖,而X是在Y之前声明的,所以Z最终采用的是X中的a,即1.1版本。并且此过程是递归的,如果X的
dependencyManagement
又
import
了工程
Q
,
对于
Z
而言,会认为在
Q
中的
dependencyManagement
就是定义在
X
中的。即 依赖管理 中的
import
的效果是直接替换,而不是像依赖仲裁一样的最近原则,但是当前工程的优先级要高于
import
进来的
import的效果可以简单理解成 把import的工程中的
dependencyManagement
递归的插入到当前位置,如果插入某个依赖时,发现当前工程中有这个依赖的定义了,就跳过此依赖,即直接声明的优先级高于import进来的
4000520066 欢迎批评指正
All Rights Reserved 新浪公司 版权所有