pom文件概要
mvn项目的核心是pom.xml文件,定义了项目的基本信息,用于描述项目如何构建和声明项目依赖等。该文件位于项目的根目录下。
pom文件的基本元素
<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.xuming.mvnbook</groupId>
<artifactId>hello-world</artifactId>
<version>1.0-SNAPSHOT</version>
<name>hello-world</name>
</project>
- pom的根元素为project
- 根元素下的第一个元素是modelVersion,指定了模型的版本,对于mvn2和mvn3来说只能是4.0.0
- groupId定义了项目的所属组,往往是公司+具体的项目,因为一个公司可能有多个项目;如 org.apache.hbase;apache的hbase项目
- artifactId 定义了当前项目在公司中的唯一id ,一般使用项目名+模块名 如:hbase-client;hbase下的client模块。
- version指定了项目的当前版本 如1.0-SNAPSHOT ;2.0等等
- name 声明了一个对于用户更为友好的项目名称,这个元素不是必须的。
mvn项目的目录结构规范
–src/main/java 项目的主代码目录
–src/main/sources 项目的资源文件目录
–src/test/java 项目的测试代码目录
–src/test/sources 项目的测试资源文件目录
mvn坐标
mvn的坐标为构建引入了秩序,任何一个构建都必须明确自己的坐标。而一组坐标是通过一系列的元素定义的,groupId、artifactId、version、packaging、classifier
- groupId定义了项目的所属组,往往是公司+具体的项目,因为一个公司可能有多个项目;如 org.apache.hbase;apache的hbase项目
- artifactId 定义了当前项目在公司中的唯一id ,一般使用项目名+模块名 如:hbase-client;hbase下的client模块。
- version指定了项目的当前版本 如1.0-SNAPSHOT ;2.0等等
- packaging 定义了构建的打包方式,默认为jar
- classifier 定义构建输出的附属构建,附属构建与主构件对应,如主构件为nexus-indexer-2.0.0.jar,改项目可能通过一些其他的插件生成nexus-indexer-2.0.0-javadoc.jar、nexus-indexer-2.0.0-sources.jar这样的附属构建,对于nexus-indexer-2.0.0.jar,对应的sources和doc就是其附属构建。注意不能直接定义项目的附属构建,因为附属构建不是项目默认生成的,而是由附加的插件帮助生成的。
以上5个元素groupId、artifactId、version是必须的,packaging是可选的(默认为jar)classifier是不能直接定义的。
mvn依赖
一个基本的依赖会包含groupId、artifactId、version等元素的组成,其实一个依赖可以包含如下的元素;
<dependencies>
<dependency>
<groupId>...</groupId>
<artifactId>...<artifactId>
<version>...</version>
<type>...</type>
<scope>...</scope>
<optional>...</optional>
<exclusions>
<exclusions>
<exclusions>
</exclusions>
</dependency>
</dependencies>
-
groupId、artifactId、version 是基本的依赖坐标,对于任何依赖来说,坐标都是最重要的,只有通过坐标才能定位一个唯一的构建
-
type 依赖类型,对应于项目定义的packaging,大部分情况下不需要配置,默认为jar
-
scope 依赖的范围
-
optional 表示是否为可选的依赖
-
exclusions 用来排出传递性依赖。
依赖范围 scope
依赖范围就是用来控制依赖与三种时期classpath(编译时classpath、测试时classpash、运行时classpath)的关系的,
maven支持的依赖范围: -
compile:编译依赖范围。默认是此依赖范围。此依赖范围对于三个时期的classpath都有效
-
test 依赖范围;此依赖范围指定测试的classpath有效,典型的样例为JUnit,
-
provided 以提供依赖范围;此依赖范围指定编译和测试有效,运行时无效,典型的样例为servlet-api
-
runtime 运行时依赖范围; 对于测试和运行时有效,编译时期无效 JDBC驱动
-
system 系统依赖范围;此依赖范围classpath作用与provider一致;但是此范围必须显示的通过systemPath指定依赖文件的路径,由于此依赖不是通过maven仓库解析的,而且与本机系统绑定,造成不可移植,应该谨慎使用。
-
import 导入依赖范围;此依赖范围不会对三种classpath造成影响。模块化的时候使用此依赖范围。
依赖范围scope | 编译classpath | 测试classpath | 运行classpath | 例子 |
---|---|---|---|---|
compile | Y | Y | Y | spring-core |
test | - | Y | - | Junit |
provider | Y | Y | - | servlet-api |
runtime | - | Y | Y | JDBC驱动 |
system | Y | Y | - | 本地的,MVN库之外的文件 |
依赖传递
当我们的项目使用spring-core.jar 时,如果我们不使用mvn,如果spring-core有其他的依赖,我们需要手动的去网上下载spring-core.jar的依赖,然后手动的将其添加的classpath中,有了mvn后,不再需要这么做,mvn会去中央仓库去找spring-core的构建(spring-core-version.pom),会将其依赖自动的导入到我们的项目中。
传递性依赖和依赖范围
依赖范围不仅可以控制依赖与三种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
依赖调节
mvn引入传递依赖机制,简化了依赖声明,大部分时候,我们只需要关注项目直接依赖了什么,不需要关注间接依赖,但依赖出现问题的时候,我们需要知道该传递依赖是从哪个路径引入的。
例如:A->B->C->X(1.0) A->D->X(2.0),X是A的传递依赖,但是两个依赖的X版本不一致。
mvn 依赖调节的两个原则:
- 路径最短原则 例如以上例子 X2.0会被传递依赖
- 第一声明者优先原则;当路径一致时,使用此原则
如果默认的调节方式不能满足项目的需求,我们可以使用排出依赖,既将传递的依赖exclusions 出去之后定义自己的依赖。
最佳实践
- 归类依赖,我们可以使用定义properties的方式,将依赖的版本抽象出来,当依赖需要升级的时候,只需要修改properties即可,
- 依赖查询相关命令
mvn dependency:list 查询当前项目的已解析依赖
mvn dependency:tree 查询当前项目的已解析依赖树
mvn dependency:analyze 依赖分析