Maven 基础知识依赖机制

Maven 基础知识依赖机制
2020年12月01日 11:51 拉勾IT培训

传递依赖

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进来的

财经自媒体联盟更多自媒体作者

新浪首页 语音播报 相关新闻 返回顶部