当前位置: 首页 > article >正文

Maven核心概念总结

Maven 介绍

Maven 官方文档是这样介绍的 Maven 的:

Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project's build, reporting and documentation from a central piece of information.

Apache Maven 的本质是一个软件项目管理和理解工具。基于项目对象模型 (Project Object Model,POM) 的概念,Maven 可以从一条中心信息管理项目的构建、报告和文档。

什么是 POM? 每一个 Maven 工程都有一个 pom.xml 文件,位于根目录中,包含项目构建生命周期的详细信息。通过 pom.xml 文件,我们可以定义项目的坐标、项目依赖、项目信息、插件信息等等配置。

对于开发者来说,Maven 的主要作用主要有 3 个:

  1. 项目构建:提供标准的、跨平台的自动化项目构建方式。
  2. 依赖管理:方便快捷的管理项目依赖的资源(jar 包),避免资源间的版本冲突问题。
  3. 统一开发结构:提供标准的、统一的项目结构。

关于 Maven 的基本使用这里就不介绍了,建议看看官网的 5 分钟上手 Maven 的教程:Maven in 5 Minutes 。

Maven 坐标

项目中依赖的第三方库以及插件可统称为构件。每一个构件都可以使用 Maven 坐标唯一标识,坐标元素包括:

  • groupId(必须): 定义了当前 Maven 项目隶属的组织或公司。groupId 一般分为多段,通常情况下,第一段为域,第二段为公司名称。域又分为 org、com、cn 等,其中 org 为非营利组织,com 为商业组织,cn 表示中国。以 apache 开源社区的 tomcat 项目为例,这个项目的 groupId 是 org.apache,它的域是 org(因为 tomcat 是非营利项目),公司名称是 apache,artifactId 是 tomcat。
  • artifactId(必须):定义了当前 Maven 项目的名称,项目的唯一的标识符,对应项目根目录的名称。
  • version(必须):定义了 Maven 项目当前所处版本。
  • packaging(可选):定义了 Maven 项目的打包方式(比如 jar,war...),默认使用 jar。
  • classifier(可选):常用于区分从同一 POM 构建的具有不同内容的构件,可以是任意的字符串,附加在版本号之后。

只要你提供正确的坐标,就能从 Maven 仓库中找到相应的构件供我们使用。

举个例子(引入阿里巴巴开源的 EasyExcel):

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.1.1</version>
</dependency>

你可以在 https://mvnrepository.com/ 这个网站上找到几乎所有可用的构件,如果你的项目使用的是 Maven 作为构建工具,那这个网站你一定会经常接触。

Maven 仓库

Maven 依赖

如果使用 Maven 构建产生的构件(例如 Jar 文件)被其他的项目引用,那么该构件就是其他项目的依赖。

依赖配置

配置信息示例

<project>
    <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>

配置说明

  • dependencies:一个 pom.xml 文件中只能存在一个这样的标签,是用来管理依赖的总标签。
  • dependency:包含在 dependencies 标签中,可以有多个,每一个表示项目的一个依赖。
  • groupId,artifactId,version(必要):依赖的基本坐标,对于任何一个依赖来说,基本坐标是最重要的,Maven 根据坐标才能找到需要的依赖。我们在上面解释过这些元素的具体意思,这里就不重复提了。
  • type(可选):依赖的类型,对应于项目坐标定义的 packaging。大部分情况下,该元素不必声明,其默认值是 jar。
  • scope(可选):依赖的范围,默认值是 compile。
  • optional(可选):标记依赖是否可选
  • exclusions(可选):用来排除传递性依赖,例如 jar 包冲突

依赖范围

classpath 用于指定 .class 文件存放的位置,类加载器会从该路径中加载所需的 .class 文件到内存中。

Maven 在编译、执行测试、实际运行有着三套不同的 classpath:

  • 编译 classpath:编译主代码有效
  • 测试 classpath:编译、运行测试代码有效
  • 运行 classpath:项目运行时有效

Maven 的依赖范围如下:

  • compile:编译依赖范围(默认),使用此依赖范围对于编译、测试、运行三种都有效,即在编译、测试和运行的时候都要使用该依赖 Jar 包。
  • test:测试依赖范围,从字面意思就可以知道此依赖范围只能用于测试,而在编译和运行项目时无法使用此类依赖,典型的是 JUnit,它只用于编译测试代码和运行测试代码的时候才需要。
  • provided:此依赖范围,对于编译和测试有效,而对运行时无效。比如 servlet-api.jar 在 Tomcat 中已经提供了,我们只需要的是编译期提供而已。
  • runtime:运行时依赖范围,对于测试和运行有效,但是在编译主代码时无效,典型的就是 JDBC 驱动实现。
  • system:系统依赖范围,使用 system 范围的依赖时必须通过 systemPath 元素显示地指定依赖文件的路径,不依赖 Maven 仓库解析,所以可能会造成建构的不可移植。

传递依赖性

依赖冲突

1、对于 Maven 而言,同一个 groupId 同一个 artifactId 下,只能使用一个 version。

<dependency>
    <groupId>in.hocg.boot</groupId>
    <artifactId>mybatis-plus-spring-boot-starter</artifactId>
    <version>1.0.48</version>
</dependency>
<!-- 只会使用 1.0.49 这个版本的依赖 -->
<dependency>
    <groupId>in.hocg.boot</groupId>
    <artifactId>mybatis-plus-spring-boot-starter</artifactId>
    <version>1.0.49</version>
</dependency>

若相同类型但版本不同的依赖存在于同一个 pom 文件,只会引入后一个声明的依赖。

2、项目的两个依赖同时引入了某个依赖。

举个例子,项目存在下面这样的依赖关系:

依赖链路一:A -> B -> C -> X(1.0)
依赖链路二:A -> D -> X(2.0)

这两条依赖路径上有两个版本的 X,为了避免依赖重复,Maven 只会选择其中的一个进行解析。

哪个版本的 X 会被 Maven 解析使用呢?

Maven 在遇到这种问题的时候,会遵循 路径最短优先声明顺序优先 两大原则。解决这个问题的过程也被称为 Maven 依赖调解

路径最短优先

依赖链路一:A -> B -> C -> X(1.0) // dist = 3
依赖链路二:A -> D -> X(2.0) // dist = 2

依赖链路二的路径最短,因此,X(2.0)会被解析使用。

不过,你也可以发现。路径最短优先原则并不是通用的,像下面这种路径长度相等的情况就不能单单通过其解决了:

依赖链路一:A -> B -> X(1.0) // dist = 2
依赖链路二:A -> D -> X(2.0) // dist = 2

因此,Maven 又定义了声明顺序优先原则。

依赖调解第一原则不能解决所有问题,比如这样的依赖关系:A->B->Y(1.0)、A-> C->Y(2.0),Y(1.0)和 Y(2.0)的依赖路径长度是一样的,都为 2。Maven 定义了依赖调解的第二原则:

声明顺序优先

在依赖路径长度相等的前提下,在 pom.xml 中依赖声明的顺序决定了谁会被解析使用,顺序最前的那个依赖优胜。该例中,如果 B 的依赖声明在 D 之前,那么 X (1.0)就会被解析使用。

<!-- A pom.xml -->
<dependencies>
    ...
    dependency B
    ...
    dependency D
</dependencies>

排除依赖

单纯依赖 Maven 来进行依赖调解,在很多情况下是不适用的,需要我们手动排除依赖。

举个例子,当前项目存在下面这样的依赖关系:

依赖链路一:A -> B -> C -> X(1.5) // dist = 3
依赖链路二:A -> D -> X(1.0) // dist = 2

根据路径最短优先原则,X(1.0) 会被解析使用,也就是说实际用的是 1.0 版本的 X。

但是!!!这会一些问题:如果 C 依赖用到了 1.5 版本的 X 中才有的一个类,运行项目就会报NoClassDefFoundError错误。如果 C 依赖用到了 1.5 版本的 X 中才有的一个方法,运行项目就会报NoSuchMethodError错误。

现在知道为什么你的 Maven 项目总是会报NoClassDefFoundErrorNoSuchMethodError错误了吧?

如何解决呢? 我们可以通过exclusion标签手动将 X(1.0) 给排除。

<dependency>
    ......
    <exclusions>
      <exclusion>
        <artifactId>x</artifactId>
        <groupId>org.apache.x</groupId>
      </exclusion>
    </exclusions>
</dependency>

一般我们在解决依赖冲突的时候,都会优先保留版本较高的。这是因为大部分 jar 在升级的时候都会做到向下兼容。

如果高版本修改了低版本的一些类或者方法的话,这个时候就不能直接保留高版本了,而是应该考虑优化上层依赖,比如升级上层依赖的版本。

还是上面的例子:

依赖链路一:A -> B -> C -> X(1.5) // dist = 3
依赖链路二:A -> D -> X(1.0) // dist = 2

我们保留了 1.5 版本的 X,但是这个版本的 X 删除了 1.0 版本中的某些类。这个时候,我们可以考虑升级 D 的版本到一个 X 兼容的版本。


http://www.kler.cn/a/454300.html

相关文章:

  • 【WRF模拟】如何得到更佳的WRF模拟效果?
  • 嵌入式单片机中Flash存储器控制与实现
  • 在Python如何用Type创建类
  • Ollama+OpenWebUI+llama3本地部署
  • Flink的Watermark水位线详解
  • 接口测试Day03-postman断言关联
  • Blender高效优化工作流程快捷小功能插件 Haggis Tools V1.1.5
  • jvm排查问题-实践追踪问题 与思路--堆内堆外内存泄漏排查方针
  • HarmonyOS NEXT 实战之元服务:静态案例效果---咖啡制作实况窗
  • css
  • 随时随地编码,高效算法学习工具—E时代IDE
  • PDF书籍《手写调用链监控APM系统-Java版》第10章 插件与链路的结合:SpringBoot环境插件获取应用名
  • Uniapp 微信小程序检测新版本并更新
  • 数据分析的常见问题及解决方案
  • 安全合规遇 AI 强援:深度驱动行业发展新引擎 | 倍孜网络CEO聂子尧出席ICT深度观察报告会!
  • C++-----------映射
  • Java Spring Boot 项目中嵌入前端静态资源:完整教程与实战案例
  • 模板方法、观察者模式、策略模式
  • Security知识点分享之高级安全安装虚拟机
  • 电商数据的安全与隐私保护:API接口的角色
  • 开源代码寻找平台总结
  • 【数据结构】【线性表】栈在算术表达式中的应用
  • McDonald‘s Event-Driven Architecture 麦当劳事件驱动架构
  • 分布式 IO 模块助力冲压机械臂产线实现智能控制
  • 基于python+django的旅游信息网站-旅游景点门票管理系统
  • 树莓集团:数字化产业园建设运营推动数字经济