Maven 坐标与依赖

Maven 的一大功能是管理项目依赖。为了能自动化地解析任何一个 Java 构件, Maven 就必须将它们唯一标识,这就依赖管理的底层基础 一一 坐标。本章将详细分析 Maven 坐标的作用,解释其每一个元素;在此基础上,再介绍如何配置 Maven,以及相关的经验和技巧,以帮助我们管理项目依赖。

1. 坐标详解

Maven 坐标为各种构件引人了秩序,任何一个构件都必须明确定义自己的坐标,而组 Maven 坐标是通过一些元素定义的,它们是 grouped、 artifact、 version、 packaging、classifier。先看一组坐标定义,如下:

<groupId>org.sonatype.neus</groupId>
<artifactId>nexus-indexer</artifactId>
<version>2.0.0</version>
<packaging>jar</packaging>

这是 nexus-indexer 的坐标定义, nexus-indexer 是一个对 Maven 仓库编纂索引并提供搜索功能的类库,它是 Nexus 项目的一个子模块。后面会详细介绍 Nexus。上述代码片段中,其坐标分别为 grouped: org.sonate.nexus、 artifact: nexus- indexer、 version: 2.0.0、 packaging: jar,没有 classifier。下面详细解释一下各个坐标元素:

  • grouped 定义当前 Maven 项目隶属的实际项目。首先, Maven 项目和实际项目不定是一对一的关系。比如 Springframework 这一实际项目,其对应的 Maven 项目会有很多,如 spring-core、 spring-context 等。这是由于 Maven 中模块的概念,因此,一个实际项目往往会被划分成很多模块。其次, grouped 不应该对应项目隶属的组织或公司。原因很简单,一个组织下会有很多实际项日,如果 grouped 只定义到组织级别,而后面我们会看到, artifact 只能定义 Maven 项目(模块),那么实际项目这个层将难以定义。最后, grouped 的表示方式与 Java 包名的表示方式类似,通常与域名反向对应。上例中, grouped 为 org.sonate.nexus,org-sonate表示 Sonatype 公司建立的一个非盈利性组织, nexus 表示 Nexus 这一实际项项目,该 groupeId 与域名 nexus.sonate.org 对应。

  • artifact 该元素定义实际项目中的一个 Maven 项目(模块),推荐的做法是使用实际项目名称作为 artifact 的前缀。比如上例中的 artifact 是 nexus-indexer,使用了实际项目名 nexus 作为前级,这样做的好处是方便寻找实际构件。在默认情况下,Maven 生成的构件,其文件名名会以 artifact 作为开头,如 nexus-indexer-2.0.0.jar,使用实际项目名称作为前缀之后,就能方便从一个 lib 文件夹中找到某个项目的一组构件。考虑有 5 个项目,每个项目都有一个 core 模块,如果没有前缀,我们会看到很多 core-1.2.jar 这样的文件,加上实际项目名前缀之后,便能很容易区分 foo-core-1. 2.jar, bar-core-1. 2.jar...

  • version 该元素定义 Maven 项目当前所处的版本,如上例中 nexus-indexer 的版本是 2.0.0。需要注意的是, Maven 定套完成的版本规范,以及快照(SNAPSHOT)的概念。第13章会详细讨论版本管理内容。

  • packaging 该元素定义 Maven 项目的打包方式。首先,打包方式通常与所生成构件的文件扩展名对应,如上例中 packaging 为 jar,最终文件名为 nexus-indexer-2.0.0.jar,而使用war 打包方式的 Maven 项目,最终生成的构件会有一个 .war 文件,不过这不是绝对的。其次,打包方式会影响到构建的生命周期,比如 jar 打包包和 war 打包会使用不同的命令。最后,当不定义 packaging 的时候, Maven 会使用默认值 jar。

  • classifier 该元素用来帮助定义构建输出的一些附属构件。附属构件与主构件对应,如上例中的主构件是 nexus-indexer-2.0.0.jar,该项目可能还会通过使用一些插件生成如 nexus- indexer-2.0.0-javadoc.jar、 nexus-indexer-2.0.0-sources.jar 这样一些附属构件,其包含了 Java 文档和源代码。这即时候, javadoc 和 sources 就是这两个附属构件的 classifier。这样,附属构件也就拥有了自己唯一的坐标。还有一个关于 classifier 的典型例子是 TestNG, TestNG 的主构件是基于 Java1.4 平台的,而它又提供了一个 classifier 为 jdk5 的附属构件。注意,不能直接定义项目的 classifier,因为附属构件不是项目直接默认生成的,而是由附加的插件帮助生成。

上述5个元素中, groupId、 artifactId、 version 是必须定义的, packaging 是可选的(默认为 jar),而 classifier 是不能直接定义的。

同时,项目构件的文件名是与坐标相对应的,一般的规则为 artifactId-version [-classifer] .packaging,[-classifier] 表示可选。比如上例 nexus-indexer 的主构件为 nexus- indexer2.0.0.jar,附属构件有 nexus-indexer-2.0.0-javadoc.jar。这里还要强调的一点是, packaging 并非一定与构件扩展名对应,比如 packaging 为 maven-plugin 的构件扩展名为 jar。

此外, Maven 仓库的布局也是基于 Maven 坐标,这一点会在介绍 Maven 仓库的时侯详细解释。理解清楚 Maven 坐标之后,我们就能开始讨论 Maven 的依赖管理了。

坐标查找:

  1. http://search.maven.org/
  2. http://mvnrepository.com/

2. 依赖的配置

上文已经罗列了一些简单的依赖配置,可以看到依赖会有基本的 groupeId、artifactId 和 version 等元素组成。其实一个依赖声明可以包含如下的一些元素:

<dependency>
<groupId></groupId>
<artifactId></artifactId>
<version></version>
<scope></scope>
<type></type>
<optional></optional>
<exclusions>
<exclusion></exclusion>
</exclusions>
</dependency>

根元素 project 下的 dependencies 可以包含一个或者多个 dependency 元素,以声明一个或者多个项目依赖。每个依赖可以包含的元素有:

  • grouped、 artifact 和 version 依赖的基本坐标,对于任何一个依赖来说,基本坐标是最重要的, Maven 根据据坐标才能找到需要的依赖。
  • type 依赖的类型,对应于项目坐标定义的 packaging。大部分情况下,该元素不必声明,其默认值为 jar。
  • scope 依赖的范围
  • optional 标记依赖是否可选,见 5.8 节。
  • exclusions 用来排除传递依赖性,见 5.9 节。

大部分依赖声明只包含基本坐标,然而在特殊情况下,他元素至关重要。本章下面的小节会对它们的原理和使用方式详细介绍。

3. 依赖的范围

Junit 依赖的测试范围是 test,测试范围用元素 scope 表示。本节将详细解释什么是测试范围,以及各种测试范围的效果和用途。

  1. Maven 在编译项目主代码的时候需要使用一套 classpath。例如,编译项目主代码的时候需要用到 spring-core,该文件以依赖的方式被引入到 classpath 中。
  2. Maven 在编译和执行测试的时候会使用另外一套 classpath。例如, Junit 就是一个很好的例子,该文件也以依赖的方式引人到测试使用的 classpath 中,不同的是这里的依赖范围是 test。
  3. 实际运行 Maven 项目的时候,又会使用一套 classpath。例如, spring-core 需要在该 classpath 中,而 Junit 则不需要。

依赖范围就是用来控制依赖与这三种 classpath(编译 classpath、测试 classpath、运行 classpath)的关系, Maven 有以下几种依赖范围:

  • compile 编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的 Maven 依赖,对于 编译、测试、运行三种 classpath 都有效。典型的例子是 sping-core,在编译、测试和运行的时候都需要使用该依赖。

  • test 测试依赖范围。使用此依赖范围的 Maven 依赖,只对于 测试 classpath 有效,在编译主代码或者运行项目的使用时将无法使用此类依赖。典型的例子是 Junit,它只有在编译测试代码及运行测试的时侯才需要。

  • provided 已提供依赖范围。使用此依赖范围的 Maven 依赖,对于 编译和测试 classpath 有效,但在运行时无效。典型的例子是 servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器已经提供,就不需要 Maven 重复地引入一遍。

  • runtime 运行时依赖范围围。使用此依赖范围的 Maven 依赖,对于 测试和运行 classpath 有效,但在编译主代码时无效。典型的例子是 JDBC 驱动实现,项目主代码的编译只需要 JDK 提供的 JDBC 接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。

  • system 系统依赖范围。该依赖与三种 classpath 的关系, 和 provided 依赖范围完全一致。但是,使用 system 范围的依赖时必须通过 systemPath 元素显式地指定依赖文件的路径。由于此类依赖不是通过 Maven 仓库解析的,而且往往与本机系统绑定,可能造成构建的不可移植,因此应该谨慎使用。 system 元素可以引用环境变量,如:

    <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( Maven2.0.9及以上) 导入依赖范围。该依赖范围不会对三种 classpath 产生实际的影响,本书将在 8.3.3 节介绍 Maven 依赖和 dependencyManagement 的时候详细介绍此依赖范围。

上述除 import 以外的各种依赖范围与三种 classpath 的关系如下表所示:

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

4. 传递性依赖

考虑项目 A 依赖 spring-core,而 spring-core 是依赖 commons- logging 的,那么导入 spring-core 后会自动导入 commons- logging,我们就说 commons- logging 是 A 的传递依赖。

4.1 传递性依赖和依赖范围

依赖范围不仅可以控制依赖与三种 classpath 的关系,还对传递性依赖产生影响。假设 A 依赖于 B,B 依赖于 C,我们说 A 对于 B 是第一直接依赖,B 对于 C 是第二直接依赖,A 对于 C 是传递性依赖。第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围,如表2所示,最左边一行表示第一直接依赖范围,最上面一行表示第二直接依赖范围,中间的交又单元格则表示传递性依赖范围。

表2 依赖范围影响传递性依赖

依赖范围 compile test provided runtime
compile - - runtime
test test - - test
provided provided - provided provided
runtime runtime - - runtime

仔细观察一下表2,可以发现这样的规律:

  1. 当第二直接依赖的范围是 compile 的时候,传递性依赖的范围与第一直接依赖的范围一致;
  2. 当第二直接依赖的范围是 test 的时候,依赖不会得以传递;
  3. 当第二直接依赖的范围是 provided 的时候,只传递第一直接依赖范围也为 provided 的依赖,且传递性依赖的范围同样为 provided;
  4. 当第二直接依赖的范围是 runtime 的时候,传递性依赖的范围与第一直接依赖的范围一致,但 compile 例外,此时传递性依赖的范围为 runtime

5. 依赖调解

5.1 调解原则

Maven 自动按照下边的原则调解:

  1. 第一声明者优先原则。在 pom.xml 文件定义依赖,先声明的依赖为准。

  2. 路径近者优先原则。例如:

    # spring-beans-4.2.4优先被依赖在 A 中,因为其被 A 依赖的路径最近
    A -> spirng-beans-4.2.4
    A -> B -> spirng-beans-4.2.4

5.2 依赖冲突解决方案

5.2.1 排除依赖

在依赖 struts2-spring-plugin 的设置中添加排除依赖,排除 spring-beans,

下边的配置表示:依赖 struts2-spring-plugin,但排除 struts2-spring-plugin 所依赖的 spring-beans。

<!-- struts2-spring-plugin依赖spirng-beans-3.0.5 -->
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-spring-plugin</artifactId>
<version>2.3.24</version>
<!-- 排除 spring-beans-->
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</exclusion>
</exclusions>
</dependency>

5.2.2 锁定版本

面对众多的依赖,有一种方法不用考虑依赖路径、声明优化等因素可以采用直接锁定版本的方法确定依赖构件的版本,版本锁定后则不考虑依赖的声明顺序或依赖的路径,以锁定的版本的为准添加到工程中,此方法在企业开发中常用。

如下的配置是锁定了 spring-beans 和 spring-context 的版本:

<dependencyManagement>
<dependencies>
<!--这里锁定版本为4.2.4 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
</dependencies>
</dependencyManagement>

注意:在工程中锁定依赖的版本并不代表在工程中添加了依赖,如果工程需要添加锁定版本的依赖则需要单独添加 标签,如下:

<dependencies>
<!--这里是添加依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
</dependencies>

上边添加的依赖并没有指定版本,原因是已在 中锁定了版本,所以在 下不需要再指定版本。

6. 可选依赖

假设有这样一个依赖关系,项目 A 依赖于项日 B,项目 B 依赖于项目 X 和 Y,B 对于 X 和 Y 的依赖都是可选依赖: A -> B、B -> X(可选)、B -> Y(可选)。根据传递性依赖的定义,如果所有这三个依赖的范围都是 compile,那那么 X、Y 就是 A 的 compile范围传递性依赖。然而,由于这里 X、Y 是可选依赖,依赖将不会得以传递。换句话说,X、Y下将不会对 A 有任何影响。

为什么要使用可选依赖这一特性呢?可能项目 B 实现了两个特性,其中的特性一依赖于 X,特性二依赖于 Y,而且这两个特性是互斥的,用户不可能同时使用两个特性。比如 B 是一个持久层隔离工具,它支持多种数据库,包括 MySQL/PostgreSQL 等。在构建这个工具包的时候,需要这两种数据库的驱动程序,但在使用这个工具包的时候,只会依赖种数据库。

项目 B 的依赖声明如下:

<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.10</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>8.4-701.jdbc3</version>
<optional>true</optional>
</dependency>
</dependencies>

上述 XML 代码片段中,使用 元素表示 mysql-connector-java 和 postgresql 这两个依赖为可选依赖,它们只会对当前项目 B 产生影响,当其他项目依赖于 B 的时候,这两个依赖不会被传递。因此,当项目 A 依赖于项目 B 的时候,如果其实际使用基于 MYSQL 数据库,那么在项目 A 中就需要显式地声明 mysq-connector-java 这一依赖,见代码:

<dependencies>
<dependency>
<groupId>com.github.binarylei</groupId>
<artifactId>project-b</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.10</version>
</dependency>
</dependencies>

最后,关于可选依赖需要说明的一点是,在理想的情况下,是不应该使用可选依赖的。前面我们可以看到,使用可选依赖的原因是某一个项目实现了多个特性,在面向对象设计中,有个单一职责性原则,意指一个类应该只有一项职责,而不是糅合太多的功能。这个原则在规划 Maven 项目的时候也同样适用。在上面的例子中,更好的做法是为 MYSQL 和 Postgresql 分别创建一个 Maven 项目,基于同样的 groupId 分配不同的的 artifactId,如 com.github.binarylei:project-b-mysql 和 com.github.binarylei:project-b-postgresql,在各自的 POM 中声明对应的 JDBC 驱动依赖,而且不使用可选依赖,用户则根据需要选择使用 projet-b-mysql 或者 project-b-postgresql。由于传递性依赖的作用,就不用再声明

JDBC 驱动依赖。

另外介绍几个 Maven 工具:

mvn dependency:list
mvn dependency:tree
mvn dependency:analyze

03 Maven 坐标与依赖的更多相关文章

  1. 4.Maven概念模型,maven的生命周期,Maven坐标,依赖管理(依赖范围,依赖声明),仓库管理,私服概念

     1 maven概念模型 2 maven的生命周期,项目构建过程 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdG90b3R1enVvcXVhbg== ...

  2. 3.Maven坐标和依赖

    1.1 何为Maven坐标 正如之前所说的,Maven的一大功能就是管理项目依赖.为了能自动化地解析任何一个Java构件,Maven就必须将它们唯一标识,这就依赖管理的底层基础——坐标. 1.2 坐标 ...

  3. maven坐标及依赖范围的学习(1)

    首先,我们先了解什么是maven的坐标(重中之重): 在这里我们可以看到那三个红色的行,基本是pom.xml中出现的最多的配置     例如这个配置:这里我们可以看到我们这个项目的pom文件中,他对名 ...

  4. 【Maven】---坐标与依赖

    Maven坐标与依赖 最近想深度学习下maven,找到一本书叫<Maven实战>,这本书讲的确实很好,唯一遗憾的是当时maven教学版本是3.0.0的,而目前已经到了3.5.4了,版本存在 ...

  5. Maven学习笔记—坐标和依赖

    Maven的坐标和依赖 1 Maven坐标 1.1 什么是Maven坐标 Maven坐标:世界上任何一组构件都可以使用Maven坐标来唯一标识,Maven坐标的元素包括groupId.artifact ...

  6. Maven学习(二) -- 坐标和依赖

    标签(空格分隔): 学习笔记 坐标 实际就像在几何中,我们用一对坐标(x, y)来表示坐标系中唯一的点:或者我们可以用(经度,纬度)来表示地球上的某一个位置,在Maven的世界中,有坐标来唯一的表示项 ...

  7. Maven之——坐标和依赖(上)

    Maven之--坐标和依赖(上) 1.    Maven坐标概念 Maven通过构件的坐标来在Maven仓库中定位到详细的构件.Maven的坐标元素包含groupId.artifactId.versi ...

  8. Maven学习笔记2(坐标和依赖)

    1.坐标 Maven坐标为各个构件建立了秩序,任何一个构件都必须明确自己的坐标,一个maven坐标是由一些元素确定的 <groupId>com.alivn.account</grou ...

  9. 《Maven实战》第5章 坐标和依赖

    5.1 Maven坐标——项目唯一标识 groupId(必须定义):定义Mavan项目隶属的实际项目,如SpringFramework,一个实际项目可包含多个Maven项目 artifactId(必须 ...

随机推荐

  1. spring boot + jpa + kotlin入门实例

    spring boot +jpa的文章网络上已经有不少,这里主要补充一下用kotlin来做. kotlin里面的data class来创建entity可以帮助我们减少不少的代码,比如现在这个User的 ...

  2. JAVA JDBC 各大数据库的连接字符串和连接类

    oracle:     driverClass:oracle.jdbc.OracleDriver     url:jdbc:oracle:thin:@127.0.0.1:1521:dbname mys ...

  3. leetcode258

    public class Solution { public int AddDigits(int num) { var str = num.ToString(); ; foreach (var c i ...

  4. PHP 程序员学数据结构与算法之《栈》

    “要成高手,必练此功”.   要成为优秀的程序员,数据结构和算法是必修的内容.而现在的Web程序员使用传统算法和数据结构都比较少,因为很多算法都是包装好的,不用我们去操心具体的实现细节,如PHP的取栈 ...

  5. eclipse web run on server 404

    eclipse真是个坑爹玩意儿,前期在idea开发的web,移到eclipse遇到各种问题 刚开始好好的,突然404,不明所以,搞了好几天 参考eclipse修改web项目部署路径 解决了问题 后来发 ...

  6. 揭秘Java架构技术体系

    Web应用,最常见的研发语言是Java和PHP. 后端服务,最常见的研发语言是Java和C/C++. 大数据,最常见的研发语言是Java和Python. 可以说,Java是现阶段中国互联网公司中,覆盖 ...

  7. windows与linux环境查看jdk安装路径

    windows: set java_home 查看jdk安装路径 java -version 查看jdk版本 linux: whereis java which java(java执行路径) echo ...

  8. rem 响应 js函数

    size();window.onresize = function(){ size();}function size(){ var htnl_o=document.getElementsByTagNa ...

  9. sublime打开文本时会记忆上次关闭时鼠标停留的位置

    sublime打开文本时会记忆上次关闭时鼠标停留的位置

  10. 批量部署ssh私钥认证

    vim  batch_sshkey.sh #!/bin/bashcd /rootcat /root/.ssh/id_rsa.pub > /root/.ssh/authorized_keysfor ...