maven学习第1节 - 简单介绍及依赖相关

本文目录说明

  1. maven安装目录介绍
  2. pom.xml 基础标签结构介绍
  3. maven依赖相关
    • 3.1 依赖配置
    • 3.2 依赖范围
      • compile
      • test
      • provided
      • runtime
      • system
      • import
    • 3.3 传递性依赖
      • 何为传递性依赖
      • 传递性依赖和依赖范围
    • 3.4 依赖调解
    • 3.5 最佳实践
      • 排除依赖
      • 归类依赖
      • 优化依赖

maven安装目录介绍

  • bin
    主要是maven的可执行文件。m2.confclassworlds的配置文件

  • boot
    只有一个文件plexus-classworlds-2.5.2.jar,是一个类加载框架,相对于默认的java类加载器,它提供了更方便的语法以方便配置,maven使用该框架加载自己的类库。对于一般的maven用户,不必关心该文件。

  • conf
    该目录下包含一个非常重要的配置文件settings.xml。直接修改该文件,就可以在机器上全局范围定制maven的行为。一般情况下,我们可以将该文件复制到 ~/.m2/目录下,在用户范围控制maven的行为。

  • lib
    该目录包含了maven运行时所需要的Java类库。

pom.xml 基础标签结构介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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.mycom.myapp</groupId>
<artifactId>myapp-core</artifactId>
<version>1.0-SNAPSHOT</version>
<name>hello world maven demo</name>
<packaging>jar</packaging>
......
</project>
  1. 上面的POM的第一行是xml的头。紧接着是project元素,project元素是所有pom.xml的根元素,它还声明了一些POM相关的命名空间和xsd元素,虽然这些属性不是必须的,但使用这些属性能够让第三方工具(如IDEA
    里面的XML编辑器)帮助我们快速编辑POM。

  2. modelVersion指定了当前POM模型的版本,对于maven2和maven3而言,只能是4.0.0

  3. groupId定义了项目所属组,这个组往往和项目所属的公司或组织有关联,如有个公司mycom,公司有个项目为myapp,那么groupId就是com.mycom.myapp
  4. artifactId定义了当前项目在组中的唯一ID, 与前面groupId对应的,如果myapp下有自模块core、web等,相应的artifactId可以为myapp-coremyapp-web等。
  5. version为当前项目的版本。(关于maven管理项目版本的升级发布将会在后文详细说明)
  6. name声明了一个对用户更友好的项目名称
  7. packaging指定了打包的类型(具体maven打包相关会在后文详谈)

其中,groupIdartifactIdversion定义了一个项目基本的坐标,在maven的世界中,任何jar、pom、war都是以这个坐标进行区分的。

maven依赖相关

依赖配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<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">
...
<dependencies>
...
<dependency>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<type>...</type>
<scope>...</scope>
<optional>...</optional>
<exclusions>
<exclusion>
<groupId>...</groupId>
<artifactId>...</artifactId>
</exclusion>
</exclusions>
</dependency>
...
</dependencies>
...
</project>

根元素project下的dependencies 中可以包含一个或多个dependency元素。每个dependency可包含的元素有:

  • groupId、artifactId和version: 描述依赖的基本坐标。
  • type: 依赖的类型,对应于项目坐标定义的packaging,默认为jar。
  • scope: 依赖的范围,详见 “依赖范围”。
  • optional: 标记依赖是否可选,详见”可选依赖”。
  • exclusions: 用来排除传递性依赖。详见”传递性依赖”

依赖范围

首先我们需要知道有三种classpath:

  • 编译classpath: 编译项目主代码时使用的classpath
  • 测试classpath: 编译和执行测试代码时的classpath
  • 运行classpath: 实际运行maven项目时用到的classpath

依赖范围就是用来控制依赖与这三种classpath的关系。Maven中有以下几种依赖范围:

依赖范围(scope) 对于编译时classpath有效 对于测试时classpath有效 对于运行时classpath有效 例子
compile Y Y Y spring-core
test —— —— Y JUnit
provided Y Y —— servlet-api
runtime —— Y Y JDBC驱动实现
system Y Y —— 本地的,Maven仓库之外的类库文件

其中说明4点:

  1. provided: 打包时就不会引入该依赖,如果classpath下已经有该依赖,则可设为provided。不过要注意版本冲突问题以及运行jar时是否会去读取指定classpath路径下的类库。
  2. runtime: 在编译代码时不起作用,只在测试和运行classpath下有效。
  3. system: 系统依赖范围。该依赖范围与三种classpath的关系等同于provided。但是使用system时必须通过systemPath元素显示地指定依赖文件的路径。由于该依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能造成构建的不可移植。因此应该谨慎使用。systemPath可以引用环境变量,如:

    1
    2
    3
    4
    5
    6
    7
    <dependency>
    <groupId>javax.sql</groupId>
    <artifactId>jdbc-stdext</artifactId>
    <version>2.0</version>
    <scope>system</scope>
    <systemPath>${JAVA_HOME}/lib/rt.jar</systemPath>
    </dependency>
  4. import: 导入依赖范围。该依赖范围不会对三种classpath产生实际的影响。import依赖范围只在 dependencyManagement元素下才有效果,使用该范围的依赖一般指向一个pom。作用是将目标pom中的dependencyManagement下的依赖配置导入并合并到当前pom的dependencyManagement元素中。

  5. dependencyManagement: 父POM用于帮助管理其所有子项的依赖项信息。如果myapp-parent项目使用dependencyManagement来定义对junit:junit:4.12的依赖关系,那么从这个继承的POM可以设置它们的依赖关系,只给出groupId=junit和artifactId=junit即可,Maven将填写父级设置的版本以及scope等信息。这种方法的好处是显而易见的。可以在一个中心位置设置依赖关系详细信息,并传播到它的所有子项目中。而如果我们想要在另外一个项目中使用与该父pom中一样的dependencyManagement配置,除了复制配置和继承两种方式之外,还可以使用我们刚刚介绍的import范围依赖将这一项配置导入。如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>com.dmx.myapp</groupId>
    <artifactId>myapp-parent</artifactId>
    <version>1.0.0</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>

我们引入了项目myapp-parent中的dependencyManagement配置。

传递性依赖

传递性依赖简单的来说就是,A 依赖了 B,而B又依赖了C。此处 A对C的依赖就是传递性依赖。我们可以这样想,如果依赖没有了传递性,这样我们A在使用B时还要考虑B使用了哪些依赖,这的确是一件很麻烦的事。Maven的传递性依赖机制可以很好的解决这一问题。
而上面A、B、C传递性依赖也并不是说B的所有依赖都会传递到A,这还是和依赖范围有关系。我们把A对B的依赖称为第一直接依赖,而B对C的依赖称为第二直接依赖,见下表-依赖范围影响传递性依赖:最左边一列表示第一直接依赖范围,最上面一行表示第二直接依赖范围。

A\B compile test provided runtime
compile compile —— —— runtime
test test —— —— test
provided provided —— provide provide
runtime runtime —— —— runtime

我们可以发现这样的规律:

  1. 先看第二依赖,如果第二依赖的范围是compile(默认),传递性依赖范围与第一直接依赖的范围一致;
  2. 如果如果第二依赖的范围是test,依赖不会得以传递;
  3. 当第二直接依赖的范围是provided时, 只会传递第一直接依赖范围也是provided依赖范围,且传递性也是provided;
  4. 当第二直接依赖范围是runtime时,传递性依赖范围与第一直接依赖范围一致,但compile例外,此时传递性依赖范围为runtime。

依赖调解

原则一: 路径最近者优先

有下面两条依赖路径: A -> B -> C -> x(1.0), A -> D -> x(2.0), x(1.0)的路径长度为3, x(2.0)的路径长度为2,因此x(2.0)会被解析使用。

原则二: 第一声明者优先

而如果路径长度相同时又怎么选择呢? 在路径长度相同的前提下,在pom中依赖声明的顺序决定了谁会被解析。会解析先被声明的那个依赖。

最佳实践

排除依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-embedder</artifactId>
<version>2.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>2.1</version>
</dependency>
...
</dependencies>

由于依赖的传递性,有时我们想要引入目标版本的依赖,因此需要先排除掉传递性依赖;排除时不用指定version元素,因为Maven解析后的依赖中不会出现groupId和artifactId相同,而version不同的依赖。

归类依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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.getui.nifi</groupId>
<artifactId>nifi-processor-encrypt-aes</artifactId>
<version>1.0.0</version>

<properties>
<nifi.version>1.7.0</nifi.version>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-api</artifactId>
<version>${nifi.version}</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-utils</artifactId>
<version>${nifi.version}</version>
</dependency>
</dependencies>
</project>

这样可以保证我们引入的nifi相关依赖都是同样的一个版本。

优化依赖

我们可以使用以下三个命令来查看依赖:

  1. list, 可以查看项目中的直接依赖及传递性依赖的版本号和依赖范围

    1
    mvn dependency:list
  2. tree, 可以看到项目的依赖树,通过依赖树可以清楚看到某个依赖是通过哪个依赖传递进来的。

    1
    mvn dependency:tree
  3. analyze, 只会分析编译主代码和编译测试代码需要用到的类,而执行测试或运行时需要的依赖它就发现不了。

    1
    mvn dependency:analyze