Maven实战02-坐标和依赖

Maven实战02—坐标和依赖

本文承接上文Maven实战01-使用入门,该文详细介绍maven中的坐标系统。

1.坐标系统

1
2
3
4
5
6
7
<!--		mybatis与SpringBoot结合的依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
<packaging>jar</packaging>
</dependency>

看上图的依赖配置,这是很普通的一个依赖配置。这是mybatisSpringboot配合的依赖。

  1. groupId:定义了当前项目隶属的实际项目。总之这个标识定义了该项目的组织级别。与Java的包名类似,比如在这个例子中org代表组织,mybatis大类下对spring下boot的支持。在坐标体系中groupid是可以重复的。

  2. artifactId:该元素定义实际项目中的一个maven项目(模块),推荐的做法是使用实际项目作为artifactId的前缀,比如在此项目中使用了mybatis-spring-boot。默认情况下,maven会以artifactId作为开头生成构建。

jar包生成情况

  1. version:该元素定义这个项目当前所处的版本,maven定义了一套版本定义约定。2.0.0.0代表该项目第二个重大版本。1.3.4-beta-2则代表第一个重大版本的第三个次要版本的第四次增量版本的beta-2里程碑。也就maven中版本号约定是<主版本>.<次版本>.<增量版本>-<里程碑版本>,如果最后面包含-SNAPSHOT则代表这个版本这是快照版本,快照版本是不稳定的版本
  2. packaging:该元素定义maven的打包方式。查看上图,如果为jar,则最终的文件名是以.jar结尾。如果是war,则最后会有.war结尾的文件。如果不定义这个标签则默认是jar。
  3. classifier:该元素是用来帮助定义构建输出的一些附属构件,通过一些插件生成mybatis-spring-boot-starter-javadoc.jar(一般最后javadoc代表着Java文档,source代表源代码)这样的附属构建。不能直接定义项目的classifier,因为附属构建不是项目直接默认生成的,而是由附加的插件帮忙生成。

一般项目构建的文件名是与坐标相对应的,比如看上图的文件名就是artifactId-version,结尾是.jar(根据打包方式的区别)

2.依赖的配置

上一节罗列了简单的依赖配置,实际上你还可能看到许多标签。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<type>...</type>
<scope>...</scope>
<optional>...</optional>
<exclusions>
<exclusion>
...
</exclusion>
</exclusions>
</dependency>
</dependencies>
  1. groupId、artifactId、version:对于任何一个依赖来说,基本坐标是最重要的,maven必须根据这些坐标来寻找依赖。
  2. type:依赖的类型,对应项目坐标定义的packaging。大部分情况下,不必生命,默认值是jar
  3. scope:依赖的范围,表示该依赖在哪些范围下生效,test就是测试环境。具体下面会介绍。
  4. optional:标记依赖是否可选,如果为true则代表在传递依赖时不会加入这个依赖,理想情况下是不应该使用可选依赖的,具体下面会介绍。
  5. exclusions:用来排除传递性依赖,你可以在exclusion标签中写入需要排除的依赖的groupId,artifactId,version来排除相对应的依赖。

3.依赖范围

上面提到了,我所定义的Junit依赖的范围是test。因为Junit就是Java用来测试的依赖,所以这个依赖只需要在测试的时候运转就行了。

maven在编译项目时需要使用一套classpath(类编译的class文件的目录,此处代表着项目运行所需要的class文件的集合)。在运行时又会使用一套classpath。依赖范围就是用来控制依赖与三种classpath(编译,测试,运行)的关系。maven有以下几种依赖范围:

  1. compile:编译依赖范围:如果没有指定,就会默认使用该范围。使用该范围,对于编译、测试、运行三种classpath都有效。比如说Spirng-core,Spring的核心包需要在各种情况下都使用。
  2. test:测试依赖范围。只对测试classpath有效,在编译主代码或运行项目时将无法使用此类依赖。
  3. provided:已提供依赖范围。对于编译和测试有效,但运行时无效。典型的例子是servlet-api,编译和测试的时候需要,但是在运行项目时,由于容器已经提供了,就不需要maven重复引入一遍了。
  4. runtime:运行时依赖范围。对于测试和运行有效,但在编译时无效。典型的例子是JDBC驱动是西安,项目代码编译时只需要JDK提供的JDBC接口,只有在执行测试或者运行时才需要实现上述接口具体的JDBC驱动。
  5. system:系统依赖范围。该依赖与provided范围一致。但是,使用system范围的依赖必须通过systemPath显式地指定依赖文件地路径。不过此类依赖不是通过maven仓库解析地,而是与本机系统绑定,可能造成构建地不可移植性,因此要谨慎使用。<systemPath>${java.home}/lib/rt.jar</systemPath>
  6. import:只能在dependencyManagement元素下使用,作用是将目标POM中的dependencyManagement配置导入并合并到当前POM的dependencyManagement中,这里听不懂也没关系,后面的文章讲继承的时候会说到。

依赖范围与classpath的关系

3.传递性依赖

springboot核心依赖中所包含的依赖

1
2
3
4
5
<!--		SpringBoot核心模块,包括自动配置支持、日志和yaml-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

如果使用过SpringBoot就知道,SpringBoot非常方便,你只需要引入一个核心依赖就可以启动项目。但你可能会想,Spring不是有那么多模块,他的其他模块的依赖呢?实际上是因为这个依赖里面包含了许多依赖,你只需要把这个坐标放在项目中,其他maven自然会帮你处理。这主要依靠于maven的传递性依赖。

简单来说:项目A—项目B—项目C(项目B使用了项目A的依赖,项目C使用了项目B的依赖)。那么项目C间接也使用了项目A的依赖,这就是循环依赖。

3.1依赖范围影响传递性依赖

依赖范围不仅可以控制依赖与三种classpath的关系,还会对传递性依赖产生影响。我们可以按照下图来理解。第一列也就是竖的那里表示第一直接依赖范围,最上面那一行表示第二直接依赖范围,中间的交叉单元格则表示传递性依赖范围。

依赖范围影响传递性依赖

虽然只有第一和第二依赖范围,但是多少级都可以抽象成对上下两级依赖范围的影响。我们用上面的项目ABC来举例。假如在项目A中存在一个jackson的依赖,依赖范围是compile,项目B的项目A依赖,依赖范围是test,那么从表上看,第一列看test,上面那行看compile由此得知在项目B中,jackson的依赖范围是test。假如项目C中项目B的依赖范围是compile,第一列看compile,上面那行看test由此得知,在项目C中是不会存在jsckson这个依赖的。

3.2 依赖调解

maven引入的依赖机制,虽然简化和方便了依赖声明,但是有时候当传递性依赖造成问题的时候,解决就比较麻烦。所以我们需要清楚地知道该依赖是从哪条依赖路径引入的。

假如,项目C有这样的依赖:C->B->C->X(1.0)、C->D->X(2.0)。C对于X的依赖有两条,而且他们的版本是不一样的,两个依赖都被解析显然是不对的,因为会造成依赖重复,因此必须选择一个。这里就存在maven依赖调节第一原则,路径最近者优先,那么显然会选择X(2.0)这个版本。不过这样显然还不够,万一两个路径都相同呢?从maven2.0.9以后,maven依赖调节第二原则,第一声明者优先。在POM中依赖声明的顺序决定了谁会被解析使用。假如依赖关系中,存在可选依赖,那么就算依赖是compile范围,这些依赖也不会得以传递。

3.3 排除依赖与归类依赖

有时候我们会需要显式地排除某些依赖,那么就可以直接使用<exclusions>

1
2
3
4
5
6
7
8
9
10
11
12
<!--		SpringBoot测试依赖,排除junit5的测试,重新引入junit4的测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</exclusion>
</exclusions>
</dependency>

有时候我们会需要统一版本,比如在Spring中版本统一,我们可以引入一个变量来表示.

1
2
3
4
5
6
7
8
9
10
11
<properties>
<my.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${my.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

3.4 优化依赖

在软件开发中,我们所写的项目尽量不要让maven来选择,确保任何一个构建只有唯一一个版本在依赖中存在。你可以通过mvn dependency:tree命令或mvn dependency:list来查看所有依赖是从哪里到哪里的,然后执行mvn dependency:analyze来分析依赖是否存在隐患。但实际上这个命令会查出别人框架很多问题,某些问题你也解决不了。当然如果硬要处理,就把依赖排除了就行了。或者假如你使用IDEA作为写代码的工具,你可以下载插件maven helper来实现类似的功能。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!