Maven pom 中配置依赖机制

依赖管理是 Maven 的一个核心特性。管理单个项目的依赖关系非常简单。管理由数百个模块组成的多模块项目和应用程序的依赖关系是可能的。Maven 使用定义良好的类路径和库版本在定义、创建和维护可重复的构建方面帮助很大。

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

pom 文件中的 dependencies 标签详解

<!--该元素描述了项目相关的所有依赖。 这些依赖组成了项目构建过程中的一个个环节。它们自动从项目定义的仓库中下载。要获取更多信息,请看项目依赖机制。 -->
<dependencies>
    <dependency>
        <!--依赖的group ID -->
        <groupId>org.apache.maven</groupId>
        <!--依赖的artifact ID -->
        <artifactId>maven-artifact</artifactId>
        <!--依赖的版本号。 在Maven 2里, 也可以配置成版本号的范围。 -->
        <version>3.8.1</version>
        <!-- 依赖类型,默认类型是jar。它通常表示依赖的文件的扩展名,但也有例外。一个类型可以被映射成另外一个扩展名或分类器。类型经常和使用的打包方式对应,
            尽管这也有例外。一些类型的例子:jar,war,ejb-client 和test-jar。如果设置 extensions 为 true,就可以在 plugin 里定义新的类型。所以前面的类型的例子不完整。 -->
        <type>jar</type>
        <!-- 依赖的分类器。分类器可以区分属于同一个POM,但不同构建方式的构件。分类器名被附加到文件名的版本号后面。例如,如果你想要构建两个单独的构件成
            JAR,一个使用Java 1.4编译器,另一个使用Java 6编译器,你就可以使用分类器来生成两个单独的JAR构件。 -->
        <classifier></classifier>
        <!--依赖范围。在项目发布过程中,帮助决定哪些构件被包括进来。欲知详情请参考依赖机制。 - compile :默认范围,用于编译 - provided:类似于编译,但支持你期待 jdk 或者容器提供,类似于classpath
            - runtime: 在执行时需要使用 - test: 用于test任务时使用 - system: 需要外在提供相应的元素。通过systemPath来取得
            - systemPath: 仅用于范围为system。提供相应的路径 - optional: 当项目自身被依赖时,标注依赖是否传递。用于连续依赖时使用 -->
        <scope>test</scope>
        <!--仅供system范围使用。注意,不鼓励使用这个元素,并且在新的版本中该元素可能被覆盖掉。该元素为依赖规定了文件系统上的路径。需要绝对路径而不是相对路径。推荐使用属性匹配绝对路径,例如${java.home}。 -->
        <systemPath></systemPath>
        <!--当计算传递依赖时, 从依赖构件列表里,列出被排除的依赖构件集。即告诉 maven 你只依赖指定的项目,不依赖项目的依赖。此元素主要用于解决版本冲突问题 -->
        <exclusions>
            <exclusion>
                <artifactId>spring-core</artifactId>
                <groupId>org.springframework</groupId>
            </exclusion>
        </exclusions>
        <!--可选依赖,如果你在项目 B 中把 C 依赖声明为可选,你就需要在依赖于B的项目(例如项目A)中显式的引用对C的依赖。可选依赖阻断依赖的传递性。 -->
        <optional>true</optional>
    </dependency>
</dependencies>

Transitive Dependencies 可传递依存关系

Maven 通过自动包含可传递依赖关系,避免了发现和指定您自己的依赖关系所需的库的需要。

从指定的远程存储库中读取依赖项的项目文件有助于实现这一特性。一般来说,这些项目的所有依赖项都用于您的项目中,项目从其父项或从其依赖项继承的任何依赖项也是如此,等等。

可以从中收集依赖项的级别数量没有限制。只有在发现循环依赖关系时才会出现问题。

有了可传递的依赖关系,包含库的图形可以迅速增长得相当大。基于这个原因,还有一些限制依赖项的特性:

依赖性中介——这决定了当依赖性遇到多个版本时,将选择工件的哪个版本。Maven 中采取了路径优先的策略。也就是说,它使用依赖树中与项目最接近的依赖项的版本。通过在项目的 POM 中显式地声明它,始终可以保证一个版本。注意,如果两个依赖项版本在依赖项树中的深度相同,则第一个声明胜出。

  A
  ├── B
  │   └── C
  │       └── D 2.0
  └── E
      └── D 1.0

在文本中,a、 b 和 c 的依赖关系定义为 a -> b -> c -> d 2.0和 a -> e -> d 1.0,那么在构建 a 时将使用 d 1.0,因为从 a 到 d 到 e 的路径较短。你可以在 a 中的 d 2.0中显式地添加一个依赖项来强制使用 d 2.0,如下所示:

  A
  ├── B
  │   └── C
  │       └── D 2.0
  ├── E
  │   └── D 1.0
  │
  └── D 2.0    

maven 依赖使用总结

直接依赖:
直接依赖优先于传递依赖,如果传递依赖的 jar 包版本冲突了,那么可以自己声明一个指定版本的依赖 jar,即可解决冲突。

路径近者优先:
如果两个依赖项版本在依赖项树中的深度最小的优先出。如果两个依赖项版本在依赖项树中的深度相同,则第一个声明胜出。

scope的依赖传递
A–>B–>C。当前项目为 A,A 依赖于 B,B 依赖于 C。知道B在A项目中的scope,那么怎么知道 C 在 A 中的 scope 呢?答案是:
当 C 是 test 或者 provided 时,C 直接被丢弃,A 不依赖 C;
否则 A 依赖 C,C的 scope 继承于 B 的 scope。

排除依赖项 和 可选依赖项

排除依赖项-Excluded Dependencies——如果项目 x 依赖于项目 y,而项目 y 依赖于项目 z,那么项目 x 的所有者可以使用 “exclusion” 元素将项目 z 显式排除为依赖项。排除依赖使用的是 optional 标签。将可选的依赖关系视为“默认情况下被排除”可能会有所帮助。

<optional>true</optional> <!-- value will be true or false only -->

Exclusions 是依赖排除(Dependency Exclusions)使用的是 exclusions 标签。

<dependency>
        <groupId>org.apache.struts</groupId>
        <artifactId>struts2-spring-plugin</artifactId>
        <version>2.3.24</version>
        <exclusions>
          <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
          </exclusion>
        </exclusions>
    </dependency>

虽然可传递依赖关系可以隐式地包含所需的依赖关系,但最好是显式地指定源代码直接使用的依赖关系。这种最佳实践证明了它的价值,尤其是当项目的依赖项改变了它们的依赖项时。

例如,假设项目 a 指定了对另一个项目 b 的依赖,而项目 b 指定了对项目 c 的依赖。如果您直接在项目 c 中使用组件,并且没有在项目 a 中指定项目 c,那么当项目 b 突然更新/移除它对项目 c 的依赖时,可能会导致构建失败。

直接指定依赖项的另一个原因是,它为您的项目提供了更好的文档: 您可以通过在项目中读取 POM 文件或者通过执行 mvn dependency:tree来了解更多信息。

Maven 还提供了 dependency:analyze 插件目标以分析依赖性: 它有助于使这种最佳实践更容易实现。

Dependency Scope 依赖项范围

这允许您只包含适用于当前生成阶段的依赖项。下面将对此进行更详细的描述。

compile
默认就是compile,什么都不配置也就是意味着 compile。compile 表示被依赖项目需要参与当前项目的编译,当然后续的测试,运行周期也参与其中,是一个比较强的依赖。打包的时候通常需要包含进去。

test
scope为test表示依赖项目仅仅参与测试相关的工作,包括测试代码的编译,执行。比较典型的如junit。

runntime
runntime 表示被依赖项目无需参与项目的编译,不过后期的测试和运行周期需要其参与。与 compile 相比,跳过编译而已,说实话在终端的项目(非开源,企业内部系统)中,和 compile 区别不是很大。比较常见的如 JSR×××的实现,对应的 API jar 是 compile 的,具体实现是 runtime 的,compile 只需要知道接口就足够了。oracle jdbc 驱动架包就是一个很好的例子,一般scope为 runntime。另外 runntime 的依赖通常和 optional 搭配使用,optional为true。我可以用 A 实现,也可以用 B 实现。

provided
provided意味着打包的时候可以不用包进去,别的设施(Web Container)会提供。事实上该依赖理论上可以参与编译,测试,运行等周期。相当于compile,但是在打包阶段做了 exclude 的动作。您可以将对 Servlet API 和相关的 javaee API 的依赖设置为所提供的范围。

system
从参与度来说,也 provided 相同,不过被依赖项不会从 maven 仓库抓,而是从本地文件系统拿,一定需要配合 systemPath 属性使用。

import
maven 2.0.9 之后可用,主要用来解决多子 pom.xml 多重继承的场景。只能用在 dependencyManagement 块中,它将 spring-boot-dependencies 中 dependencyManagement 下的 dependencies 插入到当前工程的dependencyManagement 中,所以不存在依赖传递。

举例 import 的使用

<dependencyManagement>
    <dependencies>
        <dependency>
            <!-- Import dependency management from Spring Boot -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.1.12.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

当没有<scope>import</scope>时,意思是将 spring-boot-dependencies 的 dependencies 全部插入到当前工程的 dependencies 中,并且会依赖传递。

Dependency Management 依赖关系管理

依赖项管理部分是集中依赖项信息的机制。当您有一组继承自通用父级的项目时,可以将关于依赖关系的所有信息放在通用 POM 中,并对子 POM 中的工件进行更简单的引用。

根据 dependencyManagement 部分匹配依赖引用的最小信息集实际上是{ groupId,artifactId,type,classifier }。在许多情况下,这些依赖关系将引用没有分类器的 jar 工件。这允许我们将标识设置为 { groupId,artifactId } ,因为类型字段的默认值是 jar,而默认分类器是 null。如果 type 和 classifier 不是默认值则需要手动指定。

举例

<project>
  ...
  <dependencies>
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-a</artifactId>
    </dependency>
 
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-b</artifactId>
      <!-- This is not a jar dependency, so we must specify type. -->
      <type>war</type>
    </dependency>
  </dependencies>
</project>

依赖管理部分的第二个非常重要的用途是控制传递依赖中使用的工件的版本。即锁定版本

Importing Dependencies 导入依赖项

上一节中的示例描述了如何通过继承指定托管依赖项。但是,在较大的项目中可能不可能完成这一任务,因为项目只能从单个父项目继承。为了适应这一点,项目可以从其他项目导入托管依赖项。这是通过将 POM 工件声明为一个依赖项来实现的,其作用域为“ import”。

一般这两者搭配使用。

<type>pom</type>
<scope>import</scope>

举例:

<project>
  <groupId>maven</groupId>
  <artifactId>B</artifactId>
  ...

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>maven</groupId>
        <artifactId>A</artifactId>
        <version>1.0</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
      ...
      </dependency>
    </dependencies>
  </dependencyManagement> 
  ...
</project>

Bill of Materials (BOM) POMs

当使用导入定义通常是多项目构建的一部分的相关工件的“库”时,导入是最有效的。一个项目使用这些库中的一个或多个构件是相当常见的。但是,有时很难使用工件使项目中的版本与库中分发的版本保持同步。

项目的根源是 BOM POM。它定义了将在库中创建的所有工件的版本。其他希望使用这个库的项目应该将这个 POM 导入它们 POM 的 dependencyManagement 部分。

System Dependencies

This is deprecated 已经过时了

Dependencies with the scope system 总是可用的,并且不在存储库中查找。它们通常用于告诉 Maven 由 JDK 或 VM 提供的依赖关系。因此,系统依赖对于解决构件的依赖特别有用,这些构件现在由 JDK 提供,但是在之前作为单独的下载提供。典型的例子是 JDBC 标准扩展或 JAAS 扩展(JAAS)。

一个简单的例子是:

<project>
  ...
  <dependencies>
    <dependency>
      <groupId>javax.sql</groupId>
      <artifactId>jdbc-stdext</artifactId>
      <version>2.0</version>
      <scope>system</scope>
      <systemPath>${java.home}/lib/rt.jar</systemPath>
    </dependency>
  </dependencies>
  ...
</project>

如果您的工件是由 JDK 的 tools.jar 提供的,那么系统路径的定义如下:

<project>
  ...
  <dependencies>
    <dependency>
      <groupId>sun.jdk</groupId>
      <artifactId>tools</artifactId>
      <version>1.5.0</version>
      <scope>system</scope>
      <systemPath>${java.home}/../lib/tools.jar</systemPath>
    </dependency>
  </dependencies>
  ...
</project>

参考

Maven – Introduction to the Dependency Mechanism http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html

Maven pom中的scope详解_LittleFly的博客-CSDN博客pom scope
https://blog.csdn.net/LittleFly
/article/details/79476345

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容