第二十二章 Maven

一、Maven

1. Maven 简介
Maven 是一个项目管理工具,可以对 Java 项目进行自动化的构建和依赖管理。Maven 在美国是一个口语化的词语,代表专家、内行的意思,约等于北京话中的老炮儿。有老炮儿在身边,项目经理可谓得心应手。
项目管理是一个很抽象的概念,既可以指技术上的管理工具,也可以指 "以人为本" 的管理手段(非技术因素)。无论是技术还是非技术,项目成败最大的责任人其实就是项目经理,而 Maven 就是项目经理的最得力助手。
如果我们抛开人为因素不提,仅从技术方面来说,Maven 提供了一种思想让团队更科学的管理和构建项目。Maven 采用配置文件的方式对项目的名称、版本号、项目依赖等等信息进行描述,使之结构清晰,任何人接手的成本将会变得很低。在项目构建时,利用 Maven 的 "约定大于配置" 的思想,实现自动化的构建, 如果读者熟悉 Ant,可以清醒的感知 Maven 比 Ant 脚本构建项目省去不少配置文件的内容。另外,一个项目往往依赖于其他的项目和第三方的组件才能顺利完成,Maven 提供了仓库的功能,让这些依赖项放进仓库中,项目可以随时从仓库中去取,如果其他项目组也需要这些依赖,同样也可以从仓库中去取,不必每个人去开源项目的站点去苦苦搜寻了。如此下来,人员的成本、软件维护的成本、沟通的成本、硬件的成本都降下来了。客户笑了、公司老板也笑了、项目经理笑了、团队里面的人员也笑了、Apache 社区看到这么多人在用也笑了。总之,使用 Maven 是件你好、我好、大家好的事情。
Maven 曾是 Jakarta 项目的子项目,后来成为由 Apache 软件基金会主持的独立 Apache 项目。在 Java 世界里,Jakarta 项目犹如长城一般的存在,铸就了丰功伟业。在 Apache 建立伊始,源码界犹如混沌未开化的洪荒之地,长满 C/C++ 的杂草,而 Java 刚刚漏出新芽。 当时 Apache 组织里面有很多 C 语言开发的项目,Java 还是一门小众语言。为了发展 Java Web 项目,一群有志之士聚集在一起,启动了 Jakarta 项目。 后来,Java 变得非常的火爆,以至于 Jakarta 项目囊括了众多基于 Java 语言的开源软件。最后,不得不把个别项目从 Jakarta 中独立出来,成为 Apache 软件基金会的顶级项目,例如:Struts,HttpClient,Tomcat,Ant,Maven,JMeter,Velocity,JMeter,Commons 等。一直到 2011 年 12 月,在所有子项目都被迁移为独立项目后,Jakarta 名称就不再使用了。Maven 诞生之初的目的是为了管理 Java 项目,后来 Maven 也可被用于构建和管理其他各种项目,例如 C#,Ruby,Scala 等语言编写的项目。
在 Maven 出现之前,Java 世界的项目管理工具一直由 Ant 统治着。而在此之后,又有 Gradle 逐渐在 Android 项目中作为配套打包工具流行开来。在目前看来,Maven 依旧是 Java 世界项目管理工具中的王者。
     
1.1 Maven 的组成
Maven 是一个项目管理工具,它包含了:项目对象模型 (POM,Project Object Model),项目生命周期(Project Lifecycle),依赖管理系统(Dependency Management System)和各种插件。插件主要用来实现生命周期各个阶段(phase)的目标(goal)。Maven 的组成如下所示:

     
1.2 什么是项目构建?
构建是什么呢?简单地说,构建就是软件项目生产的整个过程,这个过程应该包括:
(1)文档和代码的生成(有些项目会使用代码自动生成工具,比如数据库访问代码的逆向工程)。
(2)代码的编译、测试和打包。
(3)打包好的代码进行分发或者部署。
     
由此可见,项目的构建可绝不仅仅是编译软件这件事情。除了写代码,在项目层面做的大部分工作,都包含在构建的过程中。
有了 Maven 这个开源利器,构建中的这些过程都能够进行良好的定义,而且 Maven 能够帮我们串起来形成一个自动构建过程,这样比我们手动执行要高效得多。
   
1.3 项目依赖管理的噩梦
Java 最大的一个优势就是有非常强大的生态,整个生态中有无数的框架和 API 供人使用。我们在创建实际的项目过程中不可避免地需要用到这些框架和 API,而它们通常都是以 Jar 包的形式提供。 相信很多人都经历过 Jar Hell 的问题吧。事实上,让一个项目所依赖的外部 Jar 包保持正确的版本和最新的状态,是件非常苦逼的事情。我们编译项目的时候,需要在 ClassPath 上存放依赖的 Jar 包,而这些 Jar 包还会有其他依赖。你一定经历过递归地一个个去下载所有外部依赖的痛苦过程吧,并且还要确保下载的版本都是正确的,当项目越来越复杂的时候,这是件极其麻烦的事情。
Maven 的出现让我们获得了解脱,Maven 可以自动帮我们做依赖管理,我们需要做的就是在 POM 文件里指定依赖 Jar 包的名称、版本号,Maven 会自动下载,并递归地去下载依赖的进一步依赖。
另外,Maven 还提供一个非常方便的功能---快照依赖。快照依赖指的是那些还在开发中的内部依赖包。与其你经常地更新版本号来获取最新版本,不如直接依赖项目的快照版本。快照版本的每一个 Build 版本都会被下载到本地仓库,即使该快照版本已经在本地仓库了。使用快照依赖可以确保本地仓库中的每一个 Build 版本都是最新的,这对我们快速迭代开发是一个非常酷的特性。
   
1.4 Maven 学习要求
Maven 是 Java 项目构建工具,可以用于管理 Java 依赖,还可以用于编译、打包以及发布 Java 项目,类似于 JavaScript 生态系统中的 NPM。因此,每一位高级工程师或软件架构师,都应该至少具备以下两项 Maven 技能:
(1)熟练使用 Maven 构建项目;
(2)排查并调解项目依赖冲突;
   
2. Maven 的下载与安装
2.1 Maven 下载
Maven 官网地址是:https://maven.apache.org/index.html,如下图所示,重点关注图中红框的位置。

  
安装 Maven 3.3+ 需要 JDK 1.7 或者更高版本。
   
安装 Maven 对内存没有特殊要求,大约需要 10M 的硬盘空间即可。
   
当前版本是:Apache Maven 3.8.6,发布于 2022-06-06。
   
Maven的历史发布请移步这个页面:https://maven.apache.org/docs/history.html
   

    
Maven 官网下载地址是:https://maven.apache.org/download.cgi
   
Maven 官方下载地址:http://maven.apache.org/download.cgi,进入下载页面,找到下载文件,如下所示:

   
2.2 Maven 安装
下载完 Maven 之后,解压即可,如下图所示:

    
2.3 Maven 配置环境变量
环境变量,顾名思义,是一种变量,用于提供环境信息。它是给谁提供环境信息呢?答案是:命令行程序。
我们在命令行程序中执行某个命令的时候,操作系统是如何知道这个命令所对应的程序呢?这个时候,环境变量就被派上了用场。命令行程序从环境变量中找到这个命令所对应的程序,然后执行此程序。
接下来,我们需要配置 Maven 的环境变量。
第一步,新建第一个环境变量 MAVEN_HOME,如下所示:

   
变量名:MAVEN_HOME
变量值:D:\apache-maven-3.8.6
提醒:变量值部分根据自己的情况,自行设置即可,也就是 Maven 的解压文件目录。
第二步,编辑 Path 变量,新增:%MAVEN_HOME%\bin,如下所示:

   
提醒:
当项目比较大时,使用 Maven 生成项目需要占用大量内存,如果超过 Java 默认的最大可用内存,则会报 java.lang.OutOfMemeoryError。 解决此问题为设置 MAVEN_OPTS 环境变量,此方法一次设定,一劳永逸,为推荐方法。
MAVEN_OPTS 环境变量设置方法:在环境变量中添加一个新变量名为 MAVEN_OPTS,值为-Xms128m -Xmx512m(数值可以自定义,Xms 为初始内存,即最小内存,Xmx 为最大内存)

   
最后,验证是否安装成功。现在我们打开 cmd,输入:
mvn -v
  
会看到一些信息,如下图所示:

     
恭喜你,Maven 安装成功!
   
3. Maven 仓库
在 Maven 的术语中,仓库是一个位置。软件开发中不可避免的需要引入大量的第三方库,这些库所在的位置在 Maven 中被称为 "仓库"。 在 Maven 中任何一个依赖、插件或者项目构建的输出,都可以称之为 "构件"。Maven 仓库能帮助我们管理构件(主要是 JAR)。
   
Maven 仓库有三种类型:
- 本地仓库(local):顾名思义就是本地本机放置构件的地方。Maven 从远程仓库下载下来的构件都存放在本地仓库中,项目可以从本地 Maven 仓库中获取自己所依赖的文件。
- 中央仓库(central):Maven 中央仓库是由 Maven 社区提供的仓库,其中包含了大量常用的库。中央仓库是 Maven 官方提供的,可通过 http://search.maven.org/ 来访问。
- 远程私服(remote):私服一般是指公司内部使用的仓库。运行 Maven 时,Maven 所需要的任何构件都是从本地仓库获取的,但是如果本地仓库没有,它就会尝试
从远程仓库中下载构件到本地仓库。如果 Maven 无法连接到远程仓库,将无法正常构建项目。
    
3.1 Maven 仓库原理介绍
使用 Maven 给我们带来的最直接的好处就是统一管理 Jar 包。那么这些 Jar 包存放在哪里呢?它们就在你的本地仓库中,默认地址位于 C:\Users\用户名 \.m2 目录下(当然也可以修改这个默认地址)。

实际上,我们可以将本地仓库理解为 "缓存",目的是存放 Jar 包。开发项目时首先会从本地仓库中获取 Jar 包,当无法获取指定 Jar 包的时候,本地仓库会从远程仓库(或中央仓库)中下载 Jar 包,并 "缓存" 到本地仓库中以备将来使用。这样一来,本地仓库会随着项目的积累越变越大。

通过下面这张图可以清晰地表达项目、本地仓库、远程仓库之间的关系。

   
3.2 本地仓库
Maven 的本地仓库,在安装 Maven 后并不会创建,它是在第一次执行 Maven 命令的时候才被创建。
运行 Maven 的时候,Maven 所需要的任何构件都是直接从本地仓库获取的。如果本地仓库没有,它会首先尝试从远程仓库下载构件至本地仓库,然后再使用本地仓库的构件。
默认情况下,不管 Linux 还是 Windows,每个用户在自己的用户目录下都有一个路径名为 .m2/respository/ 的仓库目录。
Maven 本地仓库默认被创建在 %USER_HOME% 目录下。要修改默认位置,在 %M2_HOME%\conf 目录中的 Maven 的 settings.xml 文件中定义另一个路径。如下所示:
<settings>
      <localRepository>D:\apache-maven-repository\repository</localRepository>
</settings>
  
当你运行 Maven 命令,Maven 将下载依赖的文件到你指定的路径中。
   
3.3 中央仓库
Maven 中央仓库是由 Maven 社区提供的仓库,其中包含了大量常用的库。
中央仓库包含了绝大多数流行的开源 Java 构件,以及源码、作者信息、SCM、信息、许可证信息等。一般来说,简单的 Java 项目依赖的构件都可以在这里下载到。
中央仓库由 Maven 社区管理,不需要配置,但需要通过网络才能访问。
   
3.4 远程私服仓库
如果 Maven 在中央仓库中也找不到依赖的文件,它会停止构建过程并输出错误信息到控制台。为避免这种情况,Maven 提供了远程仓库的概念,它是开发人员自己定制仓库,包含了所需要的代码库或者其他工程中用到的 Jar 文件。
   
3.5 Maven 依赖搜索顺序
当我们执行 Maven 构建命令时,Maven 开始按照以下顺序查找依赖的库:
- 步骤 1 - 在本地仓库中搜索,如果找不到,执行步骤 2,如果找到了则执行其他操作。
- 步骤 2 - 在中央仓库中搜索,如果找不到,并且有一个或多个远程仓库已经设置,则执行步骤 4,如果找到了则下载到本地仓库中以备将来引用。

- 步骤 3 - 如果远程仓库没有被设置,Maven 将简单的停滞处理并抛出错误(无法找到依赖的文件)。

- 步骤 4 - 在一个或多个远程仓库中搜索依赖的文件,如果找到则下载到本地仓库以备将来引用,否则 Maven 将停止处理并抛出错误(无法找到依赖的文件)。
   
3.6 修改配置
Maven 会将下载的类库(Jar 包)放置到本地的一个目录下,如果想重新定义这个目 录的 位置 就需 要修 改 Maven 本地 仓库 的配 置。 需要修改文件:D:\apache-maven-3.8.6\conf\setting.xml,如下所示:
<settings>
  <!-- localRepository
   | The path to the local repository maven will use to store artifacts.
   | Default: ${user.home}/.m2/repository 默认存放地址
    -->
  <localRepository>D:\apache-maven-repository\repository</localRepository>
</settings>
    
依据该配置,Maven 就会将下载的类库保存到 D:\apache-maven-repository\repository 中。
验证一下我们刚才做的事情是否产生作用,请在控制台中输入:
mvn help:system
   
结果如下图所示效果:

   
如果没有任何问题,执行完该命令之后,在 D:\apache-maven-repository\repository 下面就会多出很多文件,这些文件就是 Maven 从中央仓库下载到本地仓库的文件,Maven 已经开始为我们工作了。
   
4. Maven 中央仓库地址大全
4.1 默认的 Maven 中央仓库
在 Maven 安装目录下,找到:
/lib/maven-model-builder-${version}.jar
  
打开该文件,能找到超级 POM:
\org\apache\maven\model\pom-4.0.0.xml
  
它是所有 Maven POM 的父 POM,所有 Maven 项目继承该配置,你可以在这个 POM 中发现如下配置:
<repositories>
<repository>
  <id>central</id>
  <name>Central Repository</name>
  <url>https://repo.maven.apache.org/maven2</url>
  <layout>default</layout>
  <snapshots>
	<enabled>false</enabled>
  </snapshots>
</repository>
</repositories>
   
Maven 中央库主要放置公共 jar 包,是由 Apache Maven 社区创建的,中央库的网址是:
https://repo.maven.apache.org/maven2
  
4.2 中央库是一个特殊的远程库
Maven 仓库有 3 种类型:
> Local Repository - 本地库
>
> Central Repository - 中央库
>
> Remote Repository - 远程库
   
Maven 搜索依赖项时,会按照:本地库、中央库和远程库的顺序进行。
Maven 远程库也是位于网络上的存储库。例如一个公司可能有很多共享的 jar 包文件,就可以搭建一个公司内部的远程库,供众多开发人员使用。中央库可以认为是一个特殊的远程库。
   
4.4 常用的 Maven 中央仓库地址
1. http://mvnrepository.com/
2. https://repo.maven.apache.org/maven2
   
关于 Maven 远程仓库地址的配置方式有两种:
第 1 种:直接在项目的 pom.xml 文件中进行修改(不推荐,尤其是在多人协助的开发过程中非常的费事费力);
第 2 种:将 Maven 的远程仓库统一的配置到 Maven 的 Settings.xml 的配置文件中。
   
4.5 Maven 中央仓库地址大全
1. 阿里中央仓库
   <repository>
       <id>alimaven</id>
       <name>aliyun maven</name>
       <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
   </repository>
   
2. maven.apache.org 中央仓库
   <repository>
       <id>central-repos</id>
       <name>Central Repository</name>
       <url>https://repo.maven.apache.org/maven2</url>
   </repository>
   
3. spring.io 中央仓库
   <repository>
       <id>springsource-repos</id>
       <name>SpringSource Repository</name>
       <url>http://repo.spring.io/release/</url>
   </repository>
   
5. Maven 标准项目目录
Maven 是一个项目管理工具,可以对 Java 项目进行构建、依赖管理。Maven 也可被用于构建和管理各种项目,例如 C#,Ruby,Scala 和其他语言编写的项目。Maven 提倡使用一个共同的标准目录结构,Maven 使用约定优于配置的原则,大家尽可能的遵守这样的目录结构。
   
5.1 Maven 标准项目目录示意图
Maven 提倡使用一个共同的标准目录结构,Maven 使用约定优于配置的原则,大家尽可能的遵守这样的目录结构。如下图所示:

   
5.2 Maven 标准项目目录详细说明
| 目录                                 | 作用                                                         |
| ------------------------------------ | ------------------------------------------------------------ |
| `${basedir}`                         | 存放 `pom.xml` 和所有子目录                                  |
| `${basedir}/src/main/java`           | 项目的 `java` 源代码所在的目录                               |
| `${basedir}/src/main/resources`      | 项目的资源文件所在的目录,例如:`propert`文件                |
| `${basedir}/src/test/java`           | 测试代码所在的目录,例如:`JUnit` 代码                       |
| `${basedir}/src/test/resources`      | 测试相关的资源文件所在的目录                                 |
| `${basedir}/src/main/webapp/WEB-INF` | `web` 应用文件目录,`web` 项目的信息,比如存放 `web.xml`、本地图片、`jsp` 视图页面 |
| `${basedir}/target`                  | 打包输出目录                                                 |
| `${basedir}/target/classes`          | 编译输出目录                                                 |
| `${basedir}/target/test-classes`     | 测试编译输出目录                                             |
   
6. Maven 项目创建
Maven 是 Apache 组织下的一个跨平台的项目管理工具,它主要用来帮助实现项目的构建、测试、打包和部署。Maven 提供了标准的软件生命周期模型和构建模型,通过配置就能对项目进行全面的管理。它的跨平台性保证了在不同的操作系统上可以使用相同的命令来完成相应的任务。
Maven 将构建的过程抽象成一个个的生命周期过程,在不同的阶段使用不同的已实现插件来完成相应的实际工作,这种设计方法极大的避免了设计和脚本编码的重复,极大的实现了复用。
Maven 是一个项目管理工具,可以对 Java 项目进行构建、依赖管理。Maven 也可被用于构建和管理各种项目,例如 C#,Ruby,Scala 和其他语言编写的项目。Maven 提倡使用一个共同的标准目录结构,Maven 使用约定优于配置的原则,大家尽可能的遵守这样的目录结构。
   
1. 创建新项目
打开 idea,在主菜单中选择 File --> new -->project,创建新项目

   
2. 选择 SDK
在 New Project 窗口,选择 Maven Archetyper,设置项目名称,选择项目地址,设置 JDK,选择 Maven 项目,设置 GorupId,选择 maven 项目,我们常用的就是普通项目(quickstart)和 web 项目(webapp),本次我们选择普通项目。

  
3. 工程目录
创建成功之后,我们简单看一下 Maven 的目录结构。Maven 为开发者提供了缺省的标准目录模板,好的目录结构可以使开发人员更容易理解项目,为以后的维护工作也打下良好的基础。

   
4. pom 文件
pom.xml 主要描述了项目的 maven 坐标,依赖关系,开发者需要遵循的规则,缺陷管理系统,组织和 licenses,以及其他所有的项目相关因素,是项目级别的配置文件。
我们创建的项目需要引入一些依赖的包,如果是需要引入公司内部的 jar 包,找开发要依赖坐标就可以了,如果是外部的依赖,可以在https://mvnrepository.com/ 这个网站进行查找,一般我们会选择最近更新,使用人数比较多的非 beta 版本。
  
5. settings 文件
settings 文件,一般存在与两个位置:
1.安装的地方:${M2_HOME}/conf/settings.xml
2.用户的目录:${user.home}/.m2/settings.xml
   
前者又被叫做全局配置,对操作系统的所有使用者生效;后者被称为用户配置,只对当前操作系统的使用者生效。如果两者都存在,它们的内容将被合并,并且用户范围的 settings.xml 会覆盖全局的 settings.xml。 Maven 安装后,用户目录下不会自动生成 settings.xml,只有全局配置文件。如果需要创建用户范围的 settings.xml,可以将安装路径下的 settings 复制到目录${user.home}/.m2/ Maven 默认的 settings.xml 是一个包含了注释和例子的模板,可以快速的修改它来达到你的要求。 全局配置一旦更改,所有的用户都会受到影响,而且如果 maven 进 行 升 级 , 所 有 的 配 置 都 会 被 清 除 , 所 以 要 提 前 复 制 和 备 份 `${M2_HOME}/conf/settings.xml` 文 件 , 一 般 情 况 下 不 推 荐 配 置 全 局 的settings.xml。
  
6. 仓库
settings 文件中还有一个比较重要的配置,就是仓库的配置。
在传统的项目,jar 是导入到项目中的,项目的 jar 包和代码是分开的,Maven 的 jar 包存放在仓库里。仓库分为:本地仓库、远程仓库、私服。
本地仓库,顾名思义,就是 Maven 在本地存储构件的地方。这个路径是我们自己配置的,配置成自己存放 jar 包的路径。
远程仓库,中央仓库是默认的远程仓库,Maven 在安装的时候,自带的就是中央仓库的配置,远程仓库的地址:http://repo.maven.apache.org/maven2。
私服:私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,私服代理广域网上的远程仓库,供局域网内的 Maven 用户使用。当 Maven 需要下载构件的时候,它从私服请求,如果私服上不存在该构件,则从外部的远程仓库下载。
    
私服的好处:
1、节省自己的外网带宽:减少重复请求造成的外网带宽消。
2、部署第三方构件:有些构件无法从外部仓库获得的时候,我们可以把这些构件部署到内部仓库(私服)中,供内部 maven 项目使用。
3、提高稳定性,增强控制:Internet 不稳定的时候,maven 构建也会变的不稳定,一些私服软件还提供了其他的功能。我们更新 jar 包时,先从本地仓库去找,本地没有的话就去私服上找,私服也没有的话,去中央仓库去找。
   
7. Maven 构件
在 Maven 中,任何项目输出都可成为构件。
每个构件都有自己的唯一标识,由 groupId,artifactId 和 version 等信息构成。
Maven 可以对构件进行版本控制,管理。
  
7.1 Maven 构件标识
每个构件都有自己的唯一标识(Maven 行话称之为 "唯一坐标" ),由 groupId,artifactId 和 version 等信息构成。
- groupId:当前 Maven 构件隶属的组织名。groupId 一般分为多段,通常情况下,第一段为域,第二段为公司名称。域又分为 org、com、cn 等,其中 org 为非营利组织,com 为商业组织,cn 表示中国。以 apache 开源社区的 tomcat 项目为例,这个项目的 groupId 是 org.apache,它的域是 org(因为 tomcat 是非营利项目),公司名称是 apache,artifactId 是 tomcat。(必须)
- artifactId:项目的唯一的标识符,实际对应项目的名称,就是项目根目录的名称。(必须)
- version:当前版本。(必须)
- packaging:打包方式,比如 jar,war... (必须)
- classifier:classifier 通常用于区分从同一 POM 构建的具有不同内容的构件。它是可选的,它可以是任意的字符串,附加在版本号之后。
   
7.2 Maven 的 classifier 作用
classifier 可以是任意的字符串,用于确定文件。常见的应用场景如下所示:
1. 可用于区分不同 JDK 版本所生成的 jar 包
   <dependency>
   	<groupId>net.sf.json-lib</groupId>   
   	<artifactId>json-lib</artifactId>   
   	<version>2.2.2</version>  
   	<classifier>jdk15</classifier>    
   </dependency>
   
   <dependency>  
   	<groupId>net.sf.json-lib</groupId>   
   	<artifactId>json-lib</artifactId>   
   	<version>2.2.2</version>  
   	<classifier>jdk13</classifier>    
   </dependency>
  
以上配置信息实际上对应的 jar 包是 json-lib-2.2.2-jdk15.jar 和 json-lib-2.2.2-jdk13.jar。
   
2. 区分项目的不同组成部分,例如,源代码、javadoc、类文件等。
   <dependency>
   	<groupId>net.sf.json-lib</groupId>   
   	<artifactId>json-lib</artifactId>   
   	<version>2.2.2</version>  
   	<classifier>jdk15-javadoc</classifier>    
   </dependency> 
    
以上配置信息对应的是 json-lib-2.2.2-jdk15-javadoc.jar。
  
提醒:需要注意 classifier 的位置
   <dependency>
   	<groupId>net.sf.json-lib</groupId>   
   	<artifactId>json-lib</artifactId>   
   	<classifier>jdk15-javadoc</classifier>  
   	<version>2.2.2</version>   
   </dependency> 
     
对应的是 json-lib-jdk15-javadoc-2.2.2.jar,可能会出现找不到 jar 包的情况。
  
7.3 Maven 构件特性
1. 构件具有依赖传递。例如:项目依赖构件 A,而构件 A 又依赖 B,Maven 会将 A 和 B 都视为项目的依赖。
2. 构件之间存在版本冲突时,Maven 会依据 "短路优先" 原则加载构件。此外,我们也可以在 pom.xml 中,使用 <exclusions></exclusions>显式排除某个版本的依赖,以确保项目能够运行。
- 项目依赖构件 A 和 B,构件 A → C → D(version:1.0.0),构件 B → D(version:1.1.0),此时,Maven 会优先解析加载 D(version:1.1.0)。
- 项 目 依 赖 构 件 A 和 B , 构 件 A → D(version:1.0.0) , 构 件 B → D(version:1.1.0),此时,Maven 会优先解析加载 D(version:1.0.0)。
3. 构件的依赖范围。Maven 在项目的构建过程中,会编译三套 ClassPath,分别对应:编译期,运行期,测试期。而依赖范围就是为构件指定它可以作用于哪套 ClassPath。
| -        | 编译期 | 测试期 | 运行期 | 说明                                                         |
| :------- | :----- | :----- | :----- | :----------------------------------------------------------- |
| compile  | √      | √      | √      | 默认范围                                                     |
| provided | √      | √      |        | 如 servlet-api.jar,运行期由web容器提供。                    |
| runtime  |        | √      | √      | 编译期无需直接引用。                                         |
| test     |        | √      |        | 如junit.jar。                                                |
| system   | √      | √      |        | 必须通过 <systemPath></systemPath> 元素,显示指定依赖文件的路径, 与本地系统相关联,可移植性差。 |
| import   |        |        |        | 表示继承父POM.XML中的依赖范围设置                            |
   
提醒:Maven 中的库是个物理概念,存放各种依赖 Jar,而 ClassPath 是个逻辑概念,指定所依赖 Jar 的可见性。
  
8. Maven 坐标
能够确定一个点在空间的位置的一个或一组数,叫做这个点的坐标。通常由这个点到垂直相交的若干条固定的直线的距离来表示 。这些直线叫做坐标轴。坐标轴的数目在平面上为二维空间的(x,y),在空间里为三维(x,y,z)。
Maven 的核心功能就是管理项目的依赖,引入我们所需的各种 jar 包等。为了能自动化地解析任何一个 Java 构件,Maven 必须将这些 Jar 包或者其他资源进行唯一标识,这是管理项目的依赖的基础,也就是我们要说的坐标。包括我们自己开发的项目,也是要通过坐标进行唯一标识的,这样才能才其它项目中进行依赖引用。
   
8.1 Maven 坐标组成
每个构件都有自己的唯一标识(Maven 行话称之为 **"唯一坐标"** ),由 groupId,artifactId 和 version 等信息构成。
- groupId:当前 Maven 构件隶属的组织名。groupId 一般分为多段,通常情况下,第一段为域,第二段为公司名称。域又分为 org、com、cn 等,其中 org 为非营利组织,com 为商业组织,cn 表示中国。以 apache 开源社区的 tomcat 项目为例,这个项目的 groupId 是 org.apache,它的域是 org(因为 tomcat 是非营利项目),公司名称是 apache,artifactId 是 tomcat。(必须)
- artifactId:项目的唯一的标识符,实际对应项目的名称,就是项目根目录的名称。(必须)
- version:当前版本。(必须)
- packaging:打包方式,比如 jar,war... (必须)
- classifier:classifier 通常用于区分从同一 POM 构建的具有不同内容的构件。它是可选的,它可以是任意的字符串,附加在版本号之后。
  
以 MyBatis 的坐标为例:
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.2</version>
</dependency>
   
- groupId 表示组织标识。 groupId 定义当前 Maven 项目隶属的组织机构。举个例子,如果你的公司是 mycom,有一个项目为 myapp,那么 groupId 就应该是 com.mycom.myapp。groupId 的表示方式与 Java 包名的表示方式类似。
- artifactId 表示项目名称。该元素定义当前实际项目中的一个 Maven 项目(模块),推荐的做法是使用实际项目名称作为 artifactId 的前缀。比如上例中的 mybatis,mybatis 就是实际的项目名称,方便而且直观。在默认情况下,maven 生成的构件,会以 artifactId 作为文件头,如 mybatis-3.5.2.jar,使用实际项目名称作为前缀,就能方便的从本地仓库找到某个项目的构件。
- version 表示项目的当前版本。
   
补充介绍
packaging 表示项目的打包方式。最为常见的 jar 和 war 两种,默认是 jar。定义 Maven 项目打包的方式,使用构件的什么包。首先,打包方式通常与所生成构件的文件扩展名对应,如上例中没有 packaging,则默认为 jar 包,最终的文件名为 mybatis-3.5.2.jar。
classifier 用来帮助定义构建输出的一些附件。附属构件与主构件对应,如上例中的主构件为 mybatis-3.5.2.jar,该项目可能还会通过一些插件生成如 mybatis-3.5.2-javadoc.jar,mybatis-3.5.2-sources.jar,这样附属构件也就拥有了自己唯一的坐标。
上述 5 个元素中,groupId、artifactId、version 是必须定义的,packaging 是可选的(默认为 jar),而 classfier 是不能直接定义的,需要结合插件使用。
   
9. Maven 依赖
因为 Maven 执行一系列操作,主要包括编译、测试、运行等操作,在不同的操作下依赖的 Jar 不同,依赖范围就是用来控制 Jar 包的可见性。例如,JUnit 的依赖范围是 test,只用于编译测试代码和运行测试代码的时候才可见,而在编译和运行主项目时无法使用此依赖。
有些依赖编译用不到,只有运行的时候才能用到,比如 MySQL 的驱动包在编译期就用不到(编译期用的是 JDBC 接口),而是在运行时用到的。
还有些依赖,编译期要用到,而运行期不需要提供,因为有些容器已经提供了,比如 servlet-api.jar 在 Tomcat 中已经提供了,我们只需要的是编译期提供而已。
   
9.1 Maven 库与依赖范围
Maven 中的库是个物理概念,存放各种依赖 Jar,而依赖是个逻辑概念,指定所依赖 Jar 的可见性。
这种可见性是按照编译、测试、运行等三种情况来划分的。
   
9.1.1 Maven 依赖范围
compile
编译依赖范围(默认),使用此依赖范围对于编译、测试、运行三种都有效,即在编译、测试和运行的时候都要使用该依赖 Jar 包。
   
test
测试依赖范围,从字面意思就可以知道此依赖范围只能用于测试,而在编译和运行项目时无法使用此类依赖,典型的是 JUnit,它只用于编译测试代码和运行测试代码的时候才需要。
  
provided
此依赖范围,对于编译和测试有效,而对运行时无效。比如 servlet-api.jar 在 Tomcat 中已经提供了,我们只需要的是编译期提供而已。
  
runtime
运行时依赖范围,对于测试和运行有效,但是在编译主代码时无效,典型的就是 JDBC 驱动实现。
   
system
系统依赖范围,使用 system 范围的依赖时必须通过 systemPath 元素显示地指定依赖文件的路径,不依赖 Maven 仓库解析,所以可能会造成建构的不可移植。
<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>
   
9.2 Maven 依赖冲突
在 Maven 中,依赖分为直接依赖和传递依赖(即间接依赖)。
直接依赖,顾名思义,无须赘述。对比之下,传递依赖是 Maven 的特色和重点,可大书特书。
Maven 引入的传递性依赖机制,能大大简化依赖管理,因为大部分情况下我们只需要关心项目的直接依赖是什么,而不用考虑这些直接依赖会引入什么传递性依赖。虽然 Maven 的传递依赖机制给项目构建提供了极大的便利,但是却暗藏隐患,因为依赖冲突令人抓狂。
   
9.2.1 Maven 直接依赖冲突
众所周知,对于 Maven 而言,同一个 groupId 同一个 artifactId 下,只能使用一个 version!
<dependencies>

	<dependency>
		<groupId>org.mybatis</groupId>
		<artifactId>mybatis</artifactId>
		<version>3.3.0</version>
	</dependency>

	<dependency>
		<groupId>org.mybatis</groupId>
		<artifactId>mybatis</artifactId>
		<version>3.5.0</version>
	</dependency>

</dependencies>
   
根据上列的依赖顺序,项目将使用 3.5.0 版本的 MyBatis.Jar。
现在,我们可以思考下,比如工程中需要引入 A、B,而 A 依赖 1.0 版本的 C,B 依赖 2.0 版本的 C,那么问题来了,C 使用的版本将由引入 A、B 的顺序而定?
这显然不靠谱!如果 A 的依赖写在 B 的依赖后面,将意味着最后引入的是 1.0 版本的 C,很可能在运行阶段出现类(ClassNotFoundException)、方法(NoSuchMethodError)找不到的错误(因为 B 使用的是高版本的 C)!
   
9.2.2 Maven 传递依赖冲突
Maven 引入的传递性依赖机制,能大大简化依赖管理,因为大部分情况下我们只需要关心项目的直接依赖是什么,而不用考虑这些直接依赖会引入什么传递性依赖。但是却暗藏隐患,因为依赖冲突令人抓狂。
依赖传递的发生有两种情况:一种是存在模块之间的继承关系,在继承父模块后同时引入了父模块中的依赖,可通过可选依赖机制放弃依赖传递到子模块;另一种是引包时附带引入该包所依赖的包,该方式是引起依赖冲突的主因。如下例所示:

   
例如,项目 A 有这样的依赖关系:X->Y->Z(1.0)、X->G->Z(2.0),Z 是 X 的传递性依赖,但是两条依赖路径上有两个版本的 Z,那么哪个 Z 会被 Maven 解析使用呢?两个版本都被解析显然是不对的,因为那会造成依赖重复,因此必须选择一个。
   
9.3 Maven 依赖优化
实际上 Maven 是比较智能的,它能够自动解析直接依赖和传递性依赖,根据预定义规则判断依赖范围的合理性,也可以对部分依赖进行适当调整来保证构件版本唯一。
即使这样,还会有些情况使 Maven 发生误判,因此手工进行依赖优化还是相当有必要的。我们可以使用 maven-dependency-plugin 提供的三个目标来实现依赖分析:
$ mvn dependency:list
$ mvn dependency:tree
$ mvn dependency:analyze
  
如若需更精细的分析结果,可以在命令后使用诸如以下参数:
-Dverbose
-Dincludes=<groupId>:<artifactId>
   
9.4 Maven 依赖冲突调解规则
有冲突必然有调节,总有好事佬喜欢充当调节员。调节者的立场和原则非常重要,这样才能做到公平和公正。
软件开发世界也不例外,面对 Maven 依赖冲突问题,有四种原则:
路径近者优先原则,第一声明者优先原则,排除原则和版本锁定原则。
依赖调解第一原则不能解决所有问题,比如上面这个例子,两条依赖路径长度是一样的,都为 2。那么到底谁会被解析使用呢?在 Maven 2.0.8 及之前的版本中,这是不确定的,但是从 Maven 2.0.9 开始,为了尽可能避免构建的不确定性,Maven 定义了依赖调解的第二原则:第一声明者优先。
   
9.5 Maven 依赖调解详解
Maven 依赖调解遵循以下两大原则:路径最短优先、声明顺序优先
  
第一原则:路径最近者优先
把当前模块当作顶层模块,直接依赖的包则作为次层模块,间接依赖的包则作为次层模块的次层模块,依次递推...,最后构成一棵引用依赖树。假设当前模块是 A,两种依赖路径如下所示:
  
A --> B --> X(1.1)         // dist(A->X) = 2
A --> C --> D --> X(1.0)   // dist(A->X) = 3
  
此时,Maven 可以按照第一原则自动调解依赖,结果是使用 X(1.1)作为依赖。
  
第二原则:第一声明者优先
若冲突依赖的路径长度相同,那么第一原则就无法起作用了。假设当前模块是 A,两种依赖路径如下所示:
  
A --> B --> X(1.1)   // dist(A->X) = 2
A --> C --> X(1.0)   // dist(A->X) = 2
   
当路径长度相同,则需要根据 A 直接依赖包在 pom.xml 文件中的先后顺序来判定使用那条依赖路径,如果次级模块相同则向下级模块推,直至可以判断先后位置为止。
<!-- A pom.xml -->
<dependencies>
    ...
    dependency B
    ...
    dependency C
</dependencies>
   
假设依赖 B 位置在依赖 C 之前,则最终会选择 X(1.1)依赖。
  
其它情况:覆盖策略
若相同类型但版本不同的依赖存在于同一个 pom 文件,依赖调解两大原则都不起作用,需要采用覆盖策略来调解依赖冲突,最终会引入最后一个声明的依赖。如下所示:
  
<!-- 该 pom 文件最终引入 commons-cli:commons-cli:1.3.jar 依赖包。 -->
<dependencies>

  <dependency>
    <groupId>commons-cli</groupId>
    <artifactId>commons-cli</artifactId>
    <version>1.2</version>
  </dependency>

  <dependency>
    <groupId>commons-cli</groupId>
    <artifactId>commons-cli</artifactId>
    <version>1.4</version>
  </dependency>

  <dependency>
    <groupId>commons-cli</groupId>
    <artifactId>commons-cli</artifactId>
    <version>1.3</version>
  </dependency>

</dependencies>
        
9.6 Maven 解决依赖冲突
冲突解决方式简单粗暴,直接在 pom.xml 文件中排除冲突依赖即可。
<dependency>
  <groupId>org.glassfish.jersey.containers</groupId>
  <artifactId>jersey-container-grizzly2-http</artifactId>
  <!-- 剔除依赖 -->
  <exclusions>
    <exclusion>
      <groupId>org.glassfish.hk2.external</groupId>
      <artifactId>jakarta.inject</artifactId>
    </exclusion>
    ...
  </exclusions>
</dependency>
  
10. 创建多模块工程
1、Maven项目整体结构:

  
项目主要分成11个模块,bao-project为父模块,bao-project-api、bao-project-check、bao-project-common、bao-project-dao、bao-project-elastic-job、bao-project-package、bao-project-security、bao-project-sms、bao-project-web和project-redis均为子模块。
  
2、创建父模块(bao-project)
1、依次点击:File->New->Project

   
2、左侧面板选择maven(不要选择Create from archetype选项),如下图,点击Next即可。

  
3、依次补全信息,点击Next

  
4、这就是我们就创建好了一个普通项目,因为该项目是作为一个Parent project存在的,可以直接删除src文件夹。

  
点击键盘上的Delete 键,弹出如下提示信息,选择Delete 选项,表示:删除选中的src 文件夹

   
3、创建子模块(bao-project-api)
1、在父模块上右键如下图操作,创建一个模块,该模块即为子模块。

   
2、同样不选择Create from archetype选项,因为是普通模块,Next。

   
3、依次补全信息,点击Finish

   
4、创建完成后结构如下图。

   

4、按照步骤3,创建剩余子模块(bao-project-check、bao-project-common、bao-project-dao、bao-project-elastic-job、bao-project-package、bao-project-security、bao-project-sms、bao-project-web和project-redis)

  • 22
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

方寸之间不太闲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值