maven系列-(二)maven依赖

我们在用maven的时候,最常接触到的功能就是项目依赖了,我们在pom文件里,指定依赖的各种jar包,maven就可以自动的找到jar包,下载到本地。我们的项目就可以正常运行了。
在引入包的时候,一般都是这样引入的:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.1.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

这样就可以引入spring-core和spring-context和junit三个依赖了。
maven既然能够根据pom文件中的这些标签,自动的引入依赖,那就需要定义一套规则。可以唯一的标识要引入的包,这就有了坐标的概念。

坐标:

maven坐标的元素包括groupId、artifactId、version、packaging、classifier,只要是能够提供正确的坐标,maven就可以找到对应的依赖。maven内置了一个中央仓库的地址(http://repo1.maven.org/maven2)),该仓库中就包含了全世界大部分流行的开源项目构建,maven在需要的时候就会去这个仓库下载。当然,在开发项目的时候,依然要为自己的项目指定坐标,这样其他的项目就可以通过坐标来引用了。

那定义坐标的这几个元素都代表什么意思呢?
groupId:定义了项目属于哪个组,一般和项目所在的组织或公司存在关联
artifactId:定义了项目在组中唯一的id,一般就是项目名
version:定义了版本号。如果version说明是xxx-SNAPSHOT说明这是一个快照,该项目还处于开发中,是不稳定的版本。
packaging:定义了maven项目打包的方式,一般为jar或war。默认为jar。
classifier:定义构建输出的一些附属构件,不能直接定义,是由附加的插件帮助生成的。
有了这几个元素,就可以唯一确定相应的构件依赖了。

依赖:

上面引入的依赖,还有一个更完整的写法:

<dependencies>
    <dependency>
        <groupId>...</groupId>
        <artifactId>...</artifactId>
        <version>...</version>
        <type>...</type>
        <scope>...</scope>
        <optional>...</optional>
        <exclusions>
            <exclusion>
                ...
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

这样就是一个完整的引入依赖的方式,其中,groupId、artifactId、version自然就不必说了,type是标识依赖的类型,对应项目坐标定义的packaging,一般不需要声明,默认为jar。scope是依赖的范围,optional标识依赖是否可选,exclusions用来排除传递性依赖。
一般引入依赖是,可选的依赖范围scope有compile、test、provided、runtime、system和import。默认是compile。因为我们项目引入的那些依赖是整个生命周期需要的依赖,但是编译的时候,可能只需要一部分依赖,运行单元测试的时候,也需要一部分依赖。比如,在引入Junit依赖的时候,一般声明scope为test就可以了。

compile是编译依赖范围,也是默认的依赖范围,对编译、测试和运行都有效。
test是测试依赖范围,只对测试有效。
provided是已提供依赖范围,对编译和测试有效,运行时无效。
runtime是运行时依赖范围,测试和运行时有效,编译无效。
system是系统依赖范围,和provided范围一致,但需要用systemPath显示指定依赖文件路径,一般不用。
import是导入依赖范围。

我们一开始的例子中,引入了spring-core和spring-context、junit三个依赖,如果在idea中点击进入依赖的话,会发现,其实里面还定义了很多的依赖。spring-core是spring框架的基石,它为spring框架提供了基础的支持。所以里面还会有很多依赖。
我们在maven中,只需要引入spring-core的依赖,那spring-core本身需要的那些依赖,maven也会自动的引入,不需要我们再手动的引入一遍了。这就是maven依赖中的传递性依赖。
举个例子,A依赖于B,B依赖于C,A对B就是第一直接依赖,B对C就是第二直接依赖,A对C就是传递性依赖。
但是,传递性依赖和依赖的范围也是有关系的,第一直接依赖和第二直接依赖的范围,决定了传递依赖的范围。如果我在A中依赖B,范围是test,B依赖C,范围还是test,那么,A是不会传递性依赖C的。
传递性依赖的范围,可以通过下面的表格来说明,左边一列表示第一直接依赖,上面一行表示第二直接依赖,中间的结果表示传递性依赖。

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

有了传递性依赖,我们在pom文件中引入依赖就变得简单多了,一般只需要引入需要直接依赖就可以了,剩下的maven自动会引入。但是,这种方式有时也会造成问题,在调试的时候,就会发现引入的依赖版本不符合预期,这就需要去进行依赖调解了。
在idea右侧的maven栏,点击show dependencies,可以看到所有的依赖。
在这里插入图片描述
我们在项目中引入了spring-core、spring-context和junit,就可以看到对应的依赖图。
在这里插入图片描述

因为我们项目一共就引入了三个依赖,所以依赖图比较简单,如果引入的依赖多的话,依赖图就会很复杂。
在依赖图中,就可以看到自己依赖的包版本号,找到问题的所在,把不需要传递依赖的包通过exclusions去掉。

在解决问题之后,还需要考虑下,为什么会出现引入的依赖不符合预期呢,这就因为传递依赖时,可能多个包都会依赖同一个包,而且版本还不一样,但maven不可能把这个包所有的版本都引入,最终只会引入一个包,所以需要遵循一定的原则。
maven传递依赖的原则要遵循最短路径和最先声明。先判断依赖的路径长度,最短的优先,如果长短一样,再判断声明顺序,先声明的优先。

举个例子,有两个依赖路径A->B->C, A->D->E->C,这两个依赖路径引入的C的版本号不一样。
对于A->B->C这种依赖关系,A依赖B,B依赖C,所以这条依赖路径上,A对C的传递依赖路径长度是2。
对于A->D->E->C这种依赖关系,A对C的依赖路径长度为3。比上面A->B->C的路径长度要大,所以,最终C的版本,按照A->B->C引入的依赖版本。
那对于路径长度一样的情况呢,比如两个依赖路径A->B->C, A->D->C,这两个路径,对C的依赖长度都是2,所以,就需要看声明的顺序了,第一声明优先,如果A依赖B的声明在A依赖D的声明之前,则C的版本,就会按照A->B->C路径中声明的版本。

参考资料:
1.《maven实战》 许晓斌 著

猜你喜欢

转载自blog.csdn.net/chayangdz/article/details/82905040