Maven依赖

2014/03/06 Maven

Maven依赖

Maven 一个核心的特性就是依赖管理。当我们处理多模块的项目(包含成百上千个模块或者子项目),模块间的依赖关系就变得非常复杂,管理也变得很困难。针对此种情形,Maven 提供了一种高度控制的方法。

何为Maven坐标

Maven定义了这样一组规则:世界上任务一个构件都可以使用Maven坐标唯一标示,Maven坐标元素包括groupId、artifactId、version、packaging、classifier。

Maven内置了一二中央仓库,该仓库包含世界上大部分的开源项目构件。所以需要使用坐标标示每一个jar文件。

坐标详解

①groupId:定义当前Maven项目隶属的实际项目。Maven项目和实际项目不一定一对一关系,比如SpringFramework这一实际项目,其对应的Maven项目会有很多spring-core,spring-context等,这是由于Maven中模块的概念,一个实际项目往往会被划分成很多模块。groupId不应该只对应公司,还需要定义到项目。格式应该与Java包表示方式类似,通常与域名反向一一对应。

②artifactId:该元素定义实际项目中的一个Maven项目(模块),推荐的做法是使用实际项目名称作为artifactId的前缀,模块作为后缀。

③version:定义Maven项目当前所处的版本。

④packaging:定义Maven项目的打包方式,首先,打包方式通常与所生成构件的文件扩展名对应。其次,打包方式会影响构建的生命周期,比如jar打包和war打包会使用不同的命令。默认使用jar。

⑤classifier:用来帮助定义构建输出的一些附属构建。附属构件与主构建对应,例如主构建是k12-web-1.00.jar,该项目可能还通过一些插件生成例如k12-web-1.00-javadoc.jar、k12-web-1.00-sources.jar这样两个附属构件的classifier。这样附属构件也有自己的唯一坐标。 总结:以上五个元素,groupId、artifactId、version是必须定义的,packaging是可选的,默认为jar,而classifier是不能直接定义的。

可传递性依赖发现

一种相当常见的情况,比如说 A 依赖于其他库 B。如果,另外一个项目 C 想要使用 A ,那么 C 项目也需要使用库 B。

Maven 可以避免去搜索所有所需库的需求。Maven 通过读取项目文件(pom.xml),找出它们项目之间的依赖关系。

我们需要做的只是在每个项目的 pom 中定义好直接的依赖关系。其他的事情 Maven 会帮我们搞定。

通过可传递性的依赖,所有被包含的库的图形会快速的增长。当有重复库时,可能出现的情形将会持续上升。Maven 提供一些功能来控制可传递的依赖的程度。

功能 功能描述
依赖调节 决定当多个手动创建的版本同时出现时,哪个依赖版本将会被使用。 如果两个依赖版本在依赖树里的深度是一样的时候,第一个被声明的依赖将会被使用。
依赖管理 直接的指定手动创建的某个版本被使用。例如当一个工程 C 在自己的依赖管理模块包含工程 B,即 B 依赖于 A, 那么 A 即可指定在 B 被引用时所使用的版本。
依赖范围 包含在构建过程每个阶段的依赖。
依赖排除 任何可传递的依赖都可以通过 “exclusion” 元素被排除在外。举例说明,A 依赖 B, B 依赖 C,因此 A 可以标记 C 为 “被排除的”。
依赖可选 任何可传递的依赖可以被标记为可选的,通过使用 “optional” 元素。例如:A 依赖 B, B 依赖 C。因此,B 可以标记 C 为可选的, 这样 A 就可以不再使用 C。

Maven的依赖范围

标签

在项目中导入的依赖并不一定在项目全生命周期中都要用,此时可以通过scope属性指定依赖应用的范围。

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>3.8.1</version>
    <scope>test</scope>
</dependency>

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

①compile:编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的Maven依赖,在编译、测试和运行的时候都需要使用该依赖。

②test:测试依赖范围。只对于测试classpath有效,在编译主代码或者运行项目的使用时将无法使用此依赖。典型例子是Junit。

③provided:已提供依赖范围。对于编译和测试classpath有效,但在运行时无效。典型例子是servlet-api,编译和测试项目的时候需要该依赖,但是运行项目的时候,由于容器已经提供,就不需要Maven重复的引入一遍。(servlet的创建是通过容器例如tomcat启动的时候加载文件到内存中创建的,所以不需要重复依赖)。

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

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

属性值 作用
compile ==编译范围==
scope的默认值就是compile。 在编译,测试,打包,安装,发布全部生命周期都存在的依赖资源
test ==测试范围==
运行测试代码时,才加载的依赖资源,打包,安装,发布都不参加.
runtime ==运行时范围==
和compile的唯一区别,就是不参加编译; 例如JDBC可以不写代码编译,但是必须在运行,打包安装其他阶段参加.
provided ==在编译阶段使用,但是运行,打包安装都不参加==
官方给了一个案例:servlet-api,编辑servlet,web应用等代码使用的内容,必须使用provided;(web应用,在tomcat中运行),如果将servlet-api依赖资源打包到war包,扔到tomcat执行会出现冲突;编写任何web应用时,使用到servlet-api的资源,必须添加provided的范围;
system ==系统范围==
在当前项目的环境中存在需要使用的jar包资源,maven没有提供groupId artifactId version,可以使用system指定从本地路径进行加载
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>wotongzhuo</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>D:\\piaoqian\\xxw\\xxw.jar</systemPath>
</dependency>

依赖传递性

当我们依赖的jar包,而此jar包还依赖别的jar包的时候,我们需要同时将这些依赖的jar包都依赖上,而去找却很麻烦。

account-email依赖spring-core包,而spring依赖Commons-logging。

有了传递性依赖机制,Maven会解析各个直接依赖的pom,将那些必要的间接依赖,以传递性依赖的形式引入到当前的项目中。

传递性依赖和依赖范围

假设A依赖B,B依赖C,那么A对于B是第一直接依赖,B对C是第二直接依赖,A对C是传递性依赖;第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围。

依赖调解

如果两个传递依赖都使用了相同的jar包,则选择哪个作为被解析的呢?第一原则:路径最近优先,例如A->B->C->D(1.0)和F->D(2.0),两条路径都依赖D,则按照最近的原则,则解析D(2.0)版本。

如果路径相同,则第一依赖关系在pom文件中顺序执行。哪个依赖声明靠前,解析哪个。

可选依赖

如果存在依赖A->B、B->X(可选)、B->Y(可选)。如果这三个依赖都是compile的,X、Y就是A的传递性依赖,但是X、Y是可选依赖,X、Y将不会对A有任何影响。

使用可选依赖参数解决:true

排除依赖

当我们依赖某个类库X的时候,而这个类库还依赖其他类库Y,由于传递依赖,我们也将解析Y,而Y有可能是不稳定版本,也有可能中央仓库不存在,则我们需要排除对Y的依赖而自己去声明一个对Y其他替代版本的依赖。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.7.RELEASE</version>
    <exclusions>
        <exclusion>
            <!-- 坐标指定移除内容 -->
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.3.7.RELEASE</version>
        </exclusion>
    </exclusions>
</dependency>

项目A依赖项目B,但是由于一些原因,不想引入传递依赖C,而是自己显示的生猛对于项目C1.1.0版本的依赖,例子中使用exclusions元素声明排除依赖,exclusions可以包含一个或者多个exclusion子元素。注意:声明exclusion时只需要groupId和artifactId,因为这两个元素就能唯一定位依赖图中的某个依赖,Maven解析后的依赖中,不可能出现groupId和artifactId相同但是版本不同的两个依赖。

归类依赖

例如我们依赖spring的jar包,好几个但是版本是一致的,所以如果升级的时候一起升级,所以版本统一管理更好。 例子如下:

    <properties>
        <org.springframework-version>4.0.0.RELEASE</org.springframework-version>
    </properties>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${org.springframework-version}</version>
    </dependency>

这样升级的时候一块升级,方便!!

优化依赖

Maven会自动解析所有项目的直接依赖和传递性依赖,并且根据规则正确判断每个依赖的范围,对于一些依赖冲突,也能进行调节,以确保任何一个构件只有唯一的版本在依赖中存在,这些工作之后,最后得到那些依赖被称为已解析依赖。

可以使用命令查看当前项目的已解析依赖:mvn dependency :list。或者使用mvn dependency :tree查看依赖树。

当发现项目中没有使用到的构件在依赖树种,我们可以将其删除掉。不过需要谨慎!!!!!

从仓库解析依赖的机制

当本地仓库没有依赖构件时候,Maven会自动从远程仓库下载;当依赖版本为快照版本的时候,Maven会自动找到最新的快照。依赖解析机制如下:

  • 1)当依赖范围是system,Maven直接从本地文件系统解析构件。

  • 2)根据依赖坐标计算仓库路径,先从本地仓库查询构件,如果发现则解析成功。

  • 3)本地不存在,如果依赖版本是显示的发布版本构件,如:1.2.0等,则遍历所有远程仓库,发现后下载解析使用。

  • 4)如果依赖版本是release或者latest,则基于更新策略读取所有远程仓库的元数据/groupId/artifactId/maven-metadata.xml,将其与本地仓库的对应元数据合并后,计算出release或者latest的真实的值,然后基于这个真实的值检查本地和远程仓库到2),3)。

  • 5)如果依赖版本是snapshot,则基于更新策略得到最新的快照版本的值,然后基于这个真实的值检查本地和远程仓库到2),3)。

  • 6)如果解析后得到的构件版本是时间戳格式的快照,则将时间戳改成非时间戳,并使用这个构件。

当依赖的版本不明晰的时候,如:snapshot,latest,release,Maven就需要基于更新远程仓库的策略来检查更新。上节有讲,此处不在多说。可以在命令行加上参数-U强制检查更新,使用后Maven会忽略的配置。

当Maven检查完更新策略,并决定检查依赖更新的时候,就需要检查仓库元数据maven-metadata.xml。

release版本:仓库中存在该构件的最新发布版本。

latest版本:仓库中存在该构件的最新版本(包含快照)。最新是基于/groupId/artifactId/maven-metadata.xml计算出来(远程仓库)。

注意:依赖声明的latest和release是不推荐使用的,因为Maven随时都可能解析到不同的构件,并且Maven不会明确告诉用户这种变化,如果出现构件失败,查找问题比较复杂。

仓库的元数据并不是永远正确的,当用户发现无法解析某些构件或者解析到错误构件的时候,就可能出现仓库元数据错误,需要手动的或者使用工具(如Nexus)对其进行修复。

Search

    微信好友

    博士的沙漏

    Table of Contents