Maven依赖总结
一、依赖配置
- mave依赖通过groupId、artifactId和version来确定依赖的唯一性;
- type:大多数情况下不用声明,默认为jar;
- scope:指明依赖范围;
- optional:标记依赖是否可选;
- exclusions:排除传递性依赖;
<project> ... <dependencies> ... <dependency> <groupId>...</groupId> <artifactId>...</artifactId> <version>...</version> <type>...</type> <scope>...</scope> <optional>...</optional> <exclusions> <exclusion> ... </exclusion> ... </exclusions> </dependency> ... </dependencies> ... <project>
二、依赖范围
首先需要知道maven有三套classpath,编译classpath、测试classpath和运行classpath。
- 编译classpath:指编译主代码会使用到的classpath,主代码即默认的src/main/java目录下的代码;
- 测试classpath:指编译和运行测试代码会用到的classpath,测试代码即默认的src/test/java目录下的代码;
- 运行classpath:运行主代码和打包时会用到的classpath,因为打包也是作为运行主代码所用,所以用的同一套classpath;
使用不同的依赖范围scope,可将依赖放入不同的classpath,即会在不同的生命周期或阶段生效。
- compile:编译依赖范围。默认为此依赖范围,使用此依赖范围的maven依赖,对于编译、测试和运行三种classpath都有效;
- test:测试依赖范围。只对测试classpath有效,编译、运行主代码和打包时无效,如JUnit;
- provided:已提供依赖范围。对编译classpath和测试classpath有效,运行主代码和打包时无效,如Servlet-api;
- runtime:运行时依赖范围。对运行classpath和测试classpath有效,编译主代码时无效,如mysql-connect-java;
- system:系统依赖范围。和provided依赖范围一致,使用system范围的依赖需通过systemPath显式指定依赖文件的路径。默认打包后不存在,需增加额外配置;
- import:导入依赖范围。和三种classpath没有关系。主要用来继承非父工程的依赖版本,springboot中比较常见。
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.2.RELEASE</version> </parent> <!-- 由于一个项目只能有一个父工程,所以可通过此方式继承其他工程的依赖版本--> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
依赖范围与classpath的关系
三、传递性依赖
如果A依赖于B,B依赖于C,我们说A对B是第一直接依赖,B对C是第二直接依赖,A对C是传递性依赖。传递性依赖和第一直接依赖范围、第二直接依赖范围有关。
最左边一列表示第一直接依赖范围,最上面一行表示第二直接依赖范围,则传递性依赖情况如下:
为方便记忆,总结如下:
- 当第二直接依赖的范围是complie时,传递性依赖的范围与第一直接依赖的范围一致;
- 当第二直接依赖的范围是test时,依赖不会得以传递;
- 当第二直接依赖的范围provided时,只传递第一直接依赖范围也为provided的依赖,且传递性依赖的范围同样为provided;
- 当第二直接依赖的范围时runtime时,依赖性传递的范围与第一直接依赖的范围一致,但compile例外,此时传递性依赖的范围为runtime。
四、依赖调解
maven具有依赖调解机制,当碰到如这样的依赖关系:A -> B -> C -> X(1.0)、A -> D -> X(2.0),X是A的传递性依赖,maven会根据某些原则自主决定X的依赖版本,最终X的依赖版本只会有一个。
- 第一原则,路径最近者优先:在以上例子中,X(2.0)会被解析使用;
- 第二原则,第一声明者优先:当路径一样时,如:A -> B ->Y(1.0)、A -> C -> Y(2.0),如果B的依赖声明在C之前,则Y(1.0)会被解析使用。
实际开发中,建议显式指明或排除相同依赖的版本,不应该依赖于maven的依赖调解机制。
五、可选依赖
通过optional标签指定,默认为false,即满足依赖传递的条件时,此依赖会被传递,显式指定为true时,此依赖不会被传递。
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
六、排除依赖
由于某种原因,不想引入某个传递性依赖时,或发生依赖冲突时(即同一个groupId和artifactId存在不同的version时,不应依赖自动调解),可通过exclusions排除一个或多个传递性依赖。
声明exclusion时只需要groupId和artifactId,因为maven解析后的依赖中,groupId和artifactId不会重复。
七、归类依赖
当需要依赖同一个工程的不同模块时,这些依赖的版本往往是相同的,此时可通过maven属性将版本号归类,达到避免重复、防止版本冲突、方便版本升级的目的。
如很多对Spring Framework的依赖,可统一管理版本:
<properties> <spring.version>4.1.3.RELEASE</spring.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> </dependencies>
八、优化依赖
经过maven的依赖传递机制和依赖调解,以及显式声明和排除依赖后,最终得到的那些依赖叫已解析依赖。可通过以下命令查看和优化已解析依赖:
mvn dependency:list #查看已解析依赖
mvn dependency:tree #查看依赖树,了解依赖传递信息 #注意依赖树中显示的依赖范围表示对当前工程的依赖范围
mvn dependency:analyze #分析依赖树 #Used undeclared dependencies表示使用未显式声明的依赖 #Unused declared dependencies表示未使用的显式声明的依赖 #此命令只会分析编译主代码和测试代码需要用到的依赖,不分析运行测试代码和主代码的依赖,也就是说无法分析runtime依赖范围的依赖
参考:《Maven实战》