坐标元素
Maven坐标是通过一些元素定义的,元素包括groupId、artifactId、version、packaging、classifier
groupId:定义当前Maven项目隶属的实际项目。Maven项目和实际项目不是一对一的关系。一个实际的项目对应的Maven模块可能会有很多,比如SpringFramework。groupId不应该对应项目隶属的组织或公司。groupId的一部分可以是公司或组织的关键字
artifactId:定义一个实际项目的一个Maven项目(模块)
version:Maven项目当前所处的版本,Maven定义了一套完整的版本规范以及快照的概念。
packaging:定义Maven项目的打包方式。打包方式通常与所生成构件的文件扩展名对应,比如常见的jar,最终生成的文件名为${artifactId}.${version}.jar.;使用war方式会生成一个.war文件。当不定义packaging的时候,Maven会使用默认值jar.
classifier:用来帮助定义构建输出的一些附属构件。附属构件与主构件对应,该属性不能直接定义,由附加的插件帮助生成,例如 *-javadoc.jar和*-sources.jar
依赖配置
dependencies可以包含一个或者多个dependency元素,以生命一个或者多个项目依赖,每个依赖包含的元素如下:
<dependencies>
<dependency>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<type>...</type> <!-- 对应与项目坐标定义的packaging,大部分情况下该元素不必声明,其默认值为jar。 -->
<scope>...</scope> <!-- 依赖范围 -->
<optional>...</optional> <!-- 标记依赖的范围是否可选 -->
<exclusions></exclusions> <!-- 用来排除传递性依赖,声明exclusion元素只需要groupId和artifactId,
不需要version,因为只需要根据groupId和artifactId就能唯一定位依赖图中的某一个依赖-->
</dependency>
</dependencies>
大部分依赖声明只包含基本坐标。
依赖范围
依赖范围就是用来控制依赖与三种classpath(编译classpath、测试classpath、运行classpath)的关系,Maven有以下几种依赖范围:
compile:编译依赖范围。如果没有指定就会默认使用该依赖范围。使用此依赖范围的依赖,对于编译、测试、运行三种classpath都有效。
test:测试依赖范围。使用此依赖范围的依赖只对于测试classpath有效,在编译主代码或者运行项目的时候无法使用此类依赖。比如Juint,它只有在编译测试代码及运行测试代码时才有效。
provided:已提供依赖范围。使用此依赖范围的依赖,对于编译和测试classpath有效,运行时无效。比如servlet-api。
runtime:运行时依赖范围。对于测试和运行classpath有效,但在编译主代码时无效。例如JDBC驱动
system:系统依赖范围。和provided依赖范围完全一致。但是system范围的依赖必须通过systemPath元素显示地指定依赖文件的路径。此类依赖不是通过Maven仓库解析的,往往与本机系统绑定,可能造成构建的不可移植。例如
<dependency>
<groupId>javax.sql</groupId>
<artifactId>jdbc-stdext</artifactId>
<version>2.0</version>
<scope>system</scope> <!-- 依赖范围 -->
<systemPath>${java.home}/lib/rt.jar</systemPath> <!-- 标记依赖的范围是否可选 -->
</dependency>
import:导入依赖范围。该依赖范围不会对三种classpath产生实际的影响
传递性依赖和依赖范围影
假设A依赖于B,B依赖于C,那么A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖。
第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围,左边第一类表示第一直接依赖范围,最上面一行表示第二直接依赖范围,中间交叉的单元格表示传递性依赖范围
compile | test | provided | runtime | |
compile | compile | - | - | runtime |
test | test | - | - | test |
provided | provided | - | provided | provided |
runtime | runtime | - | - | runtime |
当第二直接依赖范围是compile的时候传递性依赖的范围与第一直接依赖的范围一致;
当第二直接依赖的范围是test的时候依赖不会得以传递;
当第二直接依赖是provided的时候,只传递第一直接依赖范围也为provided的依赖,且传递性依赖的范围 同样为provided;
当第二直接依赖的范围是runtime的时候,传递性依赖的范围与第一直接依赖的范围一致,但compile例外,此时传递性依赖的范围为runtime;
依赖调解
maven依赖调解的第一原则是路径最近者优先;第二原则是第一声明者优先,在依赖路径长度相等的情况下,在POM中依赖声明的顺序决定了谁会被解析使用,顺序最靠前的那个依赖优胜。
可选依赖
项目A依赖于项目B,项目B依赖于项目X和Y,B对于项目X和Y的依赖都是可选依赖,根据传递性依赖的定义,由于X、Y是可选依赖,依赖将不会得以传递。
假如B实现了多种数据库的特性,那么B的依赖声明中就要声明多个数据库的依赖,并且都是可选的依赖,当项目A依赖于项目B时后,不同的数据库的依赖不会被传递,在项目将A的声明就要显示的声明所需要的数据库的依赖。
理想情况最好不要使用可选依赖,尽量使单个项目只有一个特性,可以将项目B拆分为多个项目,每个项目都实现单一的数据库特性,用户根据需要选择不同的依赖项目
归类依赖
如果项目中依赖了同一项目的不同模块,这些模块的版本都是一样的,如果将来需要升级的话这些依赖的版本会一起升级。比如Spring Framework的依赖,可以在pom中声明springframework.version属性,声明同一模块的不用依赖时可以引用该属性来保持版本号一致。例如:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ch.tech</groupId>
<artifactId>maven-jar</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<springframework.version>2.5.6</springframework.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${springframework.version}</version>
</dependency>
</dependencies>
</project>
优化依赖
mvn dependency:list 查看当前项目已解析依赖
mvn dependency:tree 查看当前项目依赖树
mvn dependency:analyze 分析当前项目的依赖,只会分析编译主代码和测试代码需要用到的依赖,一些执行测试和运行时需要的依赖发现不了。