Maven 依赖管理
作为构建工具最基础的就是对于依赖管理
基础
对于之前生成的项目 你可能看到了 pom.xml 文件中标签的内容
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
你会发现一个依赖存在四个元素,即 groupId、artifactId、version 和 scope,前三个可以在仓 库中唯一定位一个 jar 包的位置,简单来说就是 groupId 代表它属于哪个组织(姓),artifactId 代表它的项目名(名)。在实际开发中我们会 使用一个总依赖管理机制来统一管理版本,所以在大部分情况下我们只需要关注 groupId、artifactId 这两者即可。
Maven 从哪里获取依赖?首先 Maven 在本地存储库(默认位置)中查找所有依赖项
windows 默认位置是当前用户对应文件夹下的.m2 文件中
C:\Users\${username}\.m2\repository
linux 类似
每当项目引用本地存储库中没有的依赖时,Maven 都会将依赖从远程存储库下载到本地存储库中。
您可能注意到 Maven 在构建第一个项目时下载了很多东西(这些下载是用于构建项目的各种插件的依赖项)。
默认情况下, https://repo.maven.apache.org/maven2/ 是 Maven 使用的远程存储库。您还可以设置自己的远程存储库(可能是贵公司的中央存储库,或者镜像),以代替默认远程存储库或作为默认远程存储库的补充,当然这不是本章内容,后面会讲
举个例子 如果我需要添加一个依赖怎么办呢?
你可以先去 Maven Repository: Search/Browse/Explore (mvnrepository.com) 这个网站找到你要的包
拷贝 maven 部分的 xml 代码到 pom.xml 即可
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>4.2.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
只要你掌握了如何添加和搜索依赖,你就已经可以 cover 绝大部分常见 maven 使用的场景了
依赖传递关系
由于 java 繁荣的生态和模块化的设计哲学,各种库或多或少会依赖于其他的包,零依赖的包是非常少见的,所以会产生依赖关系,那么若两个包声明了两个相同的依赖但是版本不同那么应该引入哪个呢?这就是本节要讨论的内容
最近依赖关系
以当前项目为根的依赖树,相同距离根最近的依赖为实际使用的依赖,若距离相同则第一个声明优先
下图使用的 D 版本为 1.0
A
├── B
│ └── C
│ └── D 2.0
└── E
└── D 1.0
但是这带来了一些问题,我们假定 jar 的升级都是向下兼容的,那么 D2.0 可以无缝取代 D1.0,而反之不行,当我们真实使用时会发现报某些函数不存在的错误,这种就是典型的依赖冲突,这时候就可以利用 最近依赖关系 来解决
我们显示依赖一下 D 的 2.0 版本即可
A
├── B
│ └── C
│ └── D 2.0
├── E
│ └── D 1.0
│
└── D 2.0
这里推荐一个 idea 插件,帮助你可视化分析依赖和冲突,同时只需要点击就可以解决冲突
依赖范围
对于实际业务开发 其实没有那么复杂 大部分情况下使用默认即可
而对于你想开发一个基础的包则是必须要了解的
实际上最近依赖关系可以帮我们解决的大部分依赖冲突问题,但是并不是一个完全的解决方案,因为依赖冲突本质来源于依赖的传递性质,为了提出一个万全的方案,我们需要进一步学习依赖的范围分类(就是你在 dependency 中的 scope 标签)
依赖项作用域用于限制依赖项的可传递性,并确定依赖项何时包含在类路径(比如说要打 fat-jar 需不需要打进去)中。
- compile
这是默认作用域,如果未指定任何作用域,则使用该作用域。编译依赖项在项目的所有类路径中都可用。此外,这些依赖项将传播到上级依赖项目。即 B 依赖了 C,A 依赖 B 时会直接把 C 的依赖进来 - provided
这很像compile
但表示您希望 JDK 或容器在运行时提供依赖项。例如,在为 javaEE 构建 Web 应用程序时,应将 Servlet API 和相关 Java EE API 的依赖关系设置为provided
,因为 Web 容器提供了这些类。具有此作用域的依赖项将添加到用于编译和测试的类路径中,但不会添加到运行时类路径中。它不是可传递的。简单来说就是不打包进去 - runtime
此作用域指示依赖项不是编译所必需的,而是执行所必需的。比如可能在编译的时候只需要 JDBC API,而只有在运行的时候才需要 JDBC 驱动实现。会打包进去 - test
此作用域指示应用程序正常使用不需要这个依赖项,并且仅适用于编译测试代码和执行测试阶段。此作用域不可传递的。通常,此作用域用于测试库,如 JUnit 和 Mockito。它也用于非测试库,如 Apache Commons IO,当这些库只用于单元测试(src/test/java),但不在业务代码(src/main/java)中使用。 - system
此作用域类似于provided
,只是您必须提供显式包含它的 JAR。这个一般用于仓库里没有,但是你本地有一个,就可以利用 标签指定一个本地 jar 路径,比如说你开发了一个包,还没 install 到仓库 - import
这个只用于标签下面的依赖,用于导入一个 pom 来管理依赖
父子依赖关系的管理
我们考虑这样一个场景情况
项目一:
<project>
...
<dependencies>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-a</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>group-c</groupId>
<artifactId>excluded-artifact</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>bar</type>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
项目 B:
<project>
...
<dependencies>
<dependency>
<groupId>group-c</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>war</type>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>bar</type>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
有两个 web 项目 A、B,一个 java 项目 C,它们都需要用到同一个 jar 包:common.jar。
如果分别在三个项目的 pom 文件中定义各自对 common.jar 的依赖,那么当 common.jar 的 版本 发生变化时,三个项目的 pom 文件都要改,项目越多要改的地方就越多,很麻烦。
这时候就需要用到 parent 标签, 我们创建一个 parent 项目,打包类型为 pom,parent 项目中不存放任何代码,只是管理多个项目之间公共的依赖。在 parent 项目的 pom 文件中定义对 common.jar 的依赖,ABC 三个子项目中只需要定义 <parent>...</parent>
,parent 标签中写上 parent 项目的 pom 坐标就可以引用到 common.jar 了。
那么我们的父 POM 可以这样写
<project>
...
<dependencyManagement>
<dependencies>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-a</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>group-c</groupId>
<artifactId>excluded-artifact</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>group-c</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>war</type>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>bar</type>
<scope>runtime</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
子项目只需要用 parent 标签引入即可,然后按需声明依赖即可,版本控制就交给父级了
<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>bar</type>
</dependency>
</dependencies>
</project>
比如说实际开发中,我们的项目其实都是 spring 的一个子模块,这样就可以享受到统一依赖管理的好处了
import 范围和 parent 标签
上一节中的示例介绍如何通过继承指定托管依赖项。但是,在较大的项目中,可能无法完成此操作,因为项目只能从单个父级继承。为了适应这种情况,项目可以从其他项目导入托管依赖项。这是通过 import
作用域做到的
这里就类似于 java 的继承 你只能继承一个父类 但是可以继承很多接口
举个例子 你只需要把当前 maven 项目的 pom.xml 中的 packaging 改为 pom 那么它就变成了一个可被导入的依赖管理了
然后真正使用的时候只要这样引入即可
system 依赖
这个用于引入一个本地的 jar 包
比如 在 jdk8 的版本里面你想用 tool.jar 中的一些 api 就可以这样写
<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>
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论