当前位置:网站首页 > 技术博客 > 正文

maven入门教程



① 一个项目就是一个工程

如果项目非常庞大,就不适合使用package来划分模块,最好是每一个模块对应一个工程,利于分工协作。借助于maven就可以将一个项目拆分成多个工程

② 项目中使用jar包,需要“复制”、“粘贴”项目的lib中

同样的jar包重复的出现在不同的项目工程中,你需要做不停的复制粘贴的重复工作。借助于maven,可以将jar包保存在本地meven“仓库”中,不管在哪个项目只要使用引用即可就行。

③ jar包需要的时候每次都要自己准备好或到官网下载

借助于maven我们可以使用统一的规范方式下载jar包.

④ jar包版本不一致的风险

不同的项目在使用jar包的时候,有可能会导致各个项目的jar包版本不一致,导致未执行错误。借助于maven,所有的jar包都放在“仓库”中,所有的项目都使用仓库的一份jar包。

⑤ 一个jar包依赖其他的jar包需要自己手动的加入到项目中

FileUpload组件->IO组件,commons-fileupload-1.3.jar依赖于commons-io-2.0.1.jar

极大的浪费了我们导入包的时间成本,也极大的增加了学习成本。借助于maven,它会自动的将依赖的jar包导入进来。

Maven 是 软件基金会组织维护的一款专门为 Java 项目提供构建依赖管理支持的工具。

一个 Maven 工程有约定的目录结构,约定的目录结构对于 Maven 实现自动化构建而言是必不可少的一环,就拿自动编译来说,Maven 必须 能找到 Java 源文件,下一步才能编译,而编译之后也必须有一个准确的位置保持编译得到的文件。 我们在开发中如果需要让第三方工具或框架知道我们自己创建的资源在哪,那么基本上就是两种方式:

1.1 构建

Java 项目开发过程中,构建指的是使用『原材料生产产品』的过程。

构建过程主要包含以下环节:

1.2 依赖

Maven 中最关键的部分,我们使用 Maven 最主要的就是使用它的依赖管理功能。当 A jar 包用到了 B jar 包中的某些类时,A 就对 B 产生了依赖,那么我们就可以说 A 依赖 B。

依赖管理中要解决的具体问题:

2.1 下载安装

首页:

下载页面:

或者你也可以选择之前的版本:

然后里面选择自己对应的版本下载即可:

下载之后解压到非中文、没有空格的目录,如下:

2.2 指定本地仓库

本地仓库默认值:用户家目录/.m2/repository。由于本地仓库的默认位置是在用户的家目录下,而家目录往往是在 C 盘,也就是系统盘。将来 Maven 仓库中 jar 包越来越多,仓库体积越来越大,可能会拖慢 C 盘运行速度,影响系统性能。所以建议将 Maven 的本地仓库放在其他盘符下。配置方式如下:

本地仓库这个目录,我们手动创建一个空的目录即可。

记住:一定要把 localRepository 标签从注释中拿出来

注意:本地仓库本身也需要使用一个非中文、没有空格的目录。

2.3 配置镜像仓库

Maven 下载 jar 包默认访问境外的中央仓库,而国外网站速度很慢。改成镜像仓库,访问国内网站,可以让 Maven 下载 jar 包的时候速度更快。配置的方式是:

将原有的例子配置注释掉

加入自己的配置

2.4 配置基础 JDK 版本

如果按照默认配置运行,Java 工程使用的默认 JDK 版本是 1.5,而我们熟悉和常用的是 JDK 1.8 版本。修改配置的方式是:将 标签整个复制到 settings.xml 文件的 标签内。

2.5 配置环境变量

Maven 是一个用 Java 语言开发的程序,它必须基于 JDK 来运行,需要通过 JAVA_HOME 来找到 JDK 的安装位置。

可以使用下面的命令验证:

然后新建环境变量:

配置环境变量的规律: XXX_HOME 通常指向的是 bin 目录的上一级 PATH 指向的是 bin 目录

在配置 PATH

通过 验证:

3.1 核心概念:坐标

数学中的坐标使用 x、y、z 三个『向量』作为空间的坐标系,可以在『空间』中唯一的定位到一个『』。

Maven中的坐标使用三个『向量』在『Maven的仓库』中唯一的定位到一个『jar』包。

例如:groupId:com.tofacebook.maven

例如:artifactId:auth

例如:version:1.0.0

提示:坐标和仓库中 jar 包的存储路径之间的对应关系,如下

上面坐标对应的 jar 包在 Maven 本地仓库中的位置:

3.2 pom.xml

POM:Project Object Model,项目对象模型。和 POM 类似的是:DOM(Document Object Model),文档对象模型。它们都是模型化思想的具体体现。

POM 表示将工程抽象为一个模型,再用程序中的对象来描述这个模型。这样我们就可以用程序来管理项目了。我们在开发过程中,最基本的做法就是将现实生活中的事物抽象为模型,然后封装模型相关的数据作为一个对象,这样就可以在程序中计算与现实事物相关的数据。

POM 理念集中体现在 Maven 工程根目录下 pom.xml 这个配置文件中。所以这个 pom.xml 配置文件就是 Maven 工程的核心配置文件。其实学习 Maven 就是学这个文件怎么配置,各个配置有什么用。

3.3 依赖

上面说到我们使用 Maven 最主要的就是使用它的依赖管理功能,引入依赖存在一个范围,maven的依赖范围包括: ,,,,。

而在实际开发中,我们常用的就是 、、 。

3.4 依赖的传递

A 依赖 B,B 依赖 C,那么在 A 没有配置对 C 的依赖的情况下,A 里面能不能直接使用 C?

再以上的前提下,C 是否能够传递到 A,取决于 B 依赖 C 时使用的依赖范围。

3.5 依赖的排除

当 A 依赖 B,B 依赖 C 而且 C 可以传递到 A 的时候,A 不想要 C,需要在 A 里面把 C 排除掉。而往往这种情况都是为了避免 jar 包之间的冲突。

所以配置依赖的排除其实就是阻止某些 jar 包的传递。因为这样的 jar 包传递过来会和其他 jar 包冲突。

一般通过使用标签配置依赖的排除:

3.6 继承

3.6.1 概念

Maven工程之间,A 工程继承 B 工程

本质上是 A 工程的 pom.xml 中的配置继承了 B 工程中 pom.xml 的配置。

3.6.2 作用

在父工程中统一管理项目中的依赖信息,具体来说是管理依赖信息的版本。

它的背景是:

它背后的需求是:

通过在父工程中为整个项目维护依赖信息的组合既保证了整个项目使用规范、准确的 jar 包;又能够将以往的经验沉淀下来,节约时间和精力。

3.6.3Maven在IDEA中的应用
1. 在idea中设置maven

idea中内置maven,但一般不用,因为用内置修改maven的设置不方便

因此使用自己安装的maven,需要覆盖默认配置,也就是指定maven的安装位置等信息

其中:Setting选项是用来设置本项目的Maven的

Other Setting是设置以后建立的项目的Maven的

目录如下:

进入Setting项,可以看到有三个项目管理工具

其中,框选的两个项目工具最常用,Gradle是最新开发的项目管理工具,功能更强但应用较少。

打开Maven,框选的三个分别是Maven工具的存放路径、配置文件的存放路径、本地仓库(target文件)的存放路径

2. maven-Runner选项的配置

1. 变得更快

接下来进入Runner选项,配置vm项,可以让maven创建的更快

原本的maven默认下载一个模板文件,有7M,下载很慢,为了不让他下载,就需要在vmOption中进行配置,禁用相关的下载。

2. 自动刷新功能

若想让Maven自动刷新,即一旦更新pom.xm文件,Maven项目就自动刷新,只需勾选红框内的选项即可

3. 对新创建的项目的Maven配置

操作与对本项目进行Maven配置一致

4. Maven创建Java项目

需要选择的Maven模板:

使用普通Java项目模板创建即可

5.Maven创建Web项目

需要选择的Maven模板:

3.6.4 创建父子工程

① 一般在模块化开发中一般都会创建一个父工程,如下

父工程创建好之后,要修改它的打包方式:

只有打包方式为 pom 的 Maven 工程能够管理其他 Maven 工程。打包方式为 pom 的 Maven 工程中不写业务代码,它是专门管理其他 Maven 工程的工程,所以可以将生成的 src 目录删除。

② 创建模块工程

然后可以再父工程的 pom 文件中看到:

子工程的 pom 如下:

③ 在父工程中配置依赖的统一管理

使用标签配置对依赖的管理,如下:

而实际上被管理的依赖并没有真正被引入到工程

④ 子工程中引用那些被父工程管理的依赖

关键点:省略版本号

子工程引用父工程中的依赖信息时,可以把版本号去掉。把版本号去掉就表示子工程中这个依赖的版本由父工程决定,具体来说是由父工程的dependencyManagement来决定。

子工程 pom 如下:

此时,被管理的依赖才被引入到工程

⑤ 修改父工程依赖信息的版本

这个修改可以是降级,也可以是升级,但一般来说都是升级。

⑥ 父工程中声明自定义属性

对同一个的一组 jar 包最好使用相同的版本,为了方便升级框架,可以将 jar 包的版本信息统一提取出来,统一声明版本号 :

在需要的地方使用的形式来引用自定义的属性名,真正实现一处修改,处处生效

编写一套符合要求、开发各种功能都能正常工作的依赖组合并不容易。如果公司里已经有人总结了成熟的组合方案,那么再开发新项目时,如果不使用原有的积累,而是重新摸索,会浪费大量的时间。为了提高效率,我们可以使用工程继承的机制,让成熟的依赖组合方案能够保留下来。如下:

如上图所示,公司级的父工程中管理的就是成熟的依赖组合方案,各个新项目、子系统各取所需即可。

3.7 聚合

聚合,指分散的聚集到一起,即部分组成整体。

3.7.1 Maven 中的聚合

使用一个总工程将各个模块工程汇集起来,作为一个整体对应完整的项目,实际就是 标签。

3.7.2 继承和聚合的对应关系

从继承关系角度来看:

从聚合关系角度来看:

3.7.3 聚合的配置

在总工程中配置 modules 即可:

3.7.4 依赖循环问题

如果 A 工程依赖 B 工程,B 工程依赖 C 工程,C 工程又反过来依赖 A 工程,那么在执行构建操作时会报下面的错误:

这个错误的含义是:循环引用。

3.8 全局变量

全局变量的定义格式:

对于:自定义全局变量,一般用来自定义版本号。做法是:先使用全局变量定义,再使用

3.9 指定资源插件

一般来说,如果不指定任何资源插件,和 两个目录中的所有*.java文件会被编译,并将结果分别存放到和目录中

但是这两个目录中的其他文件都会被忽略掉,因此,如果我们需要使用其他文件,在运行时就会报错。 解决办法是指定资源插件,将以下内容放到<buid>标签中,将其他文件也进行编译

在实际使用 Maven 的过程中,我们会发现 build 标签有时候有,有时候没,这是怎么回事呢?其实通过有效 POM 我们能够看到,build 标签的相关配置其实一直都在,只是在我们需要定制构建过程的时候才会通过配置 build 标签覆盖默认值或补充配置。这一点我们可以通过打印有效 POM 来看到。

打印有效 pom mvn help:effective-pom

当默认配置无法满足需求的定制构建的时候,就需要使用 build 标签。

4.1 build 标签的组成

build 标签的子标签大致包含三个主体部分:

4.1.1 定义约定的目录结构

各个目录的作用如下:

4.1.2 备用插件管理

pluginManagement 标签存放着几个极少用到的插件:

通过 pluginManagement 标签管理起来的插件就像 dependencyManagement 一样,子工程使用时可以省略版本号,起到在父工程中统一管理版本的效果。

4.1.3 生命周期插件

plugins 标签存放的是默认生命周期中实际会用到的插件,这些插件想必大家都不陌生,所以抛开插件本身不谈,plugin 标签的结构如下:

① 坐标部分

artifactId 和 version 标签定义了插件的坐标,作为 Maven 的自带插件这里省略了 groupId。

② 执行部分

executions 标签内可以配置多个 execution 标签,execution 标签内:

goals 标签中可以配置多个 goal 标签,表示一个生命周期环节可以对应当前插件的多个目标。

4.2 典型应用:指定 JDK 版本

前面我们在 settings.xml 中配置了 JDK 版本,那么将来把 Maven 工程部署都服务器上,脱离了 settings.xml 配置,如何保证程序正常运行呢?思路就是我们直接把 JDK 版本信息告诉负责编译操作的 maven-compiler-plugin 插件,让它在构建过程中,按照我们指定的信息工作。如下:

4.3 典型应用:SpringBoot 定制化打包

很显然 spring-boot-maven-plugin 并不是 Maven 自带的插件,而是 SpringBoot 提供的,用来改变 Maven 默认的构建行为。具体来说是改变打包的行为。默认情况下 Maven 调用 maven-jar-plugin 插件的 jar 目标,生成普通的 jar 包。

普通 jar 包没法使用 java -jar xxx.jar 这样的命令来启动、运行,但是 SpringBoot 的设计理念就是每一个『微服务』导出为一个 jar 包,这个 jar 包可以使用 java -jar xxx.jar 这样的命令直接启动运行。

这样一来,打包的方式肯定要进行调整。所以 SpringBoot 提供了 spring-boot-maven-plugin 这个插件来定制打包行为。

管理依赖最基本的办法是继承父工程,但是和 Java 类一样,Maven 也是单继承的。如果不同体系的依赖信息封装在不同 POM 中了,没办法继承多个父工程怎么办?这时就可以使用 import 依赖范围。

5.1 import

典型案例当然是在项目中引入 SpringBoot、SpringCloud 依赖:

import 依赖范围使用要求:

官网说明如下: This scope is only supported on a dependency of type in the section. It indicates the dependency is to be replaced with the effective list of dependencies in the specified POM’s section. Since they are replaced, dependencies with a scope of do not actually participate in limiting the transitivity of a dependency.

5.2 system

以 Windows 系统环境下开发为例,假设现在 想要引入到我们的项目中,此时我们就可以将依赖配置为 system 范围:

但是很明显:这样引入依赖完全不具有可移植性,所以不要使用

5.3 runtime

专门用于编译时不需要,但是运行时需要的 jar 包。比如:编译时我们根据接口调用方法,但是实际运行时需要的是接口的实现类。典型案例是:

6.1 profile 概述

这里我们可以对接 profile 这个单词中『侧面』这个含义:项目的每一个运行环境,相当于是项目整体的一个侧面。

通常情况下,我们项目至少有三种运行环境:

而我们这里的『环境』仍然只是一个笼统的说法,实际工作中一整套运行环境会包含很多种不同服务器:

就拿其中的 MySQL 来说,不同环境下的访问参数肯定完全不同,可是代码只有一套。如果在 jdbc.properties 里面来回改,那就太麻烦了,而且很容易遗漏或写错,增加调试的难度和工作量。所以最好的办法就是把适用于各种不同环境的配置信息分别准备好,部署哪个环境就激活哪个配置。

在 Maven 中,使用 profile 机制来管理不同环境下的配置信息。但是解决同类问题的类似机制在其他框架中也有,而且从模块划分的角度来说,持久化层的信息放在构建工具中配置也违反了『高内聚,低耦合』的原则。

实际上,即使我们在 pom.xml 中不配置 profile 标签,也已经用到 profile了。为什么呢?因为根标签 project 下所有标签相当于都是在设定默认的 profile。这样一来我们也就很容易理解下面这句话:project 标签下除了 modelVersion 和坐标标签之外,其它标签都可以配置到 profile 中。

6.2 profile 配置

6.2.1 外部视角:配置文件

从外部视角来看,profile 可以在下面两种配置文件中配置:

6.2.2 内部实现:具体标签

从内部视角来看,配置 profile 有如下语法要求:

① profiles/profile 标签

② id 标签

每个 profile 都必须有一个 id 标签,指定该 profile 的唯一标识。这个 id 标签的值会在命令行调用 profile 时被用到。这个命令格式是:

③ 其它允许出现的标签

一个 profile 可以覆盖项目的最终名称、项目依赖、插件配置等各个方面以影响构建行为。

6.3 激活 profile

① 默认配置默认被激活

前面提到了,POM 中没有在 profile 标签里的就是默认的 profile,当然默认被激活。

② 基于环境信息激活

环境信息包含:JDK 版本、操作系统参数、文件、属性等各个方面。一个 profile 一旦被激活,那么它定义的所有配置都会覆盖原来 POM 中对应层次的元素。可参考下面的标签结构:

这里有个问题是:多个激活条件之间是什么关系呢?

下面我们来看一个具体例子。假设有如下 profile 配置,在 JDK 版本为 1.6 时被激活:

这里需要指出的是:Maven 会自动检测当前环境安装的 JDK 版本,只要 JDK 版本是以 1.6 开头都算符合条件。下面几个例子都符合:

6.4 Maven profile 多环境管理

在开发过程中,我们的软件会面对不同的运行环境,比如开发环境、测试环境、生产环境,而我们的软件在不同的环境中,有的配置可能会不一样,比如数据源配置、日志文件配置、以及一些软件运行过程中的基本配置,那每次我们将软件部署到不同的环境时,都需要修改相应的配置文件,这样来回修改,很容易出错,而且浪费劳动力。

因此我们可以利用 Maven 的 profile 来进行定义多个 profile,然后每个profile对应不同的激活条件和配置信息,从而达到不同环境使用不同配置信息的效果。

在 idea 中可以看到,因此,当你需要打包哪一个环境的就勾选即可:

同时,SpringBoot 天然支持多环境配置,一般来说,存放公共的配置,、、分别存放三个环境的配置。如下:

中配置(或者dev、test)指定使用的配置文件,如下:

注:,就是上面我们自定义的标签。

然后当我们勾选哪一个环境,打包的配置文件就是那一个环境:

同时我们再在 resource 标签下看到 includes 和 excludes 标签。它们的作用是:

很多公司都是搭建自己的 Maven 私有仓库,主要用于项目的公共模块的迭代更新等。

7.1 Nexus 下载安装

下载地址:

百度网盘:https://pan.baidu.com/s/12IjpSSUSZa6wHZoQ8wHsxg (提取码:5bu6)

百度链接: (提取码: tfb1 )

然后将下载的文件上传到 Linux 系统,解压后即可使用,不需要安装。但是需要注意:必须提前安装 JDK。(我这里放在 /root/nexus 下)

解压后如下:

通过以下命令启动:

如果显示则说明启动失败,通过命令查看端口占用情况:

可以看到 8081 端口被占用,而 nexus 的默认端口为 8081,我们可以修改其默认端口号,其配置文件在 目录下的,如下:

打开后修改为自己需要设置的端口,注意开启对外防火墙:

提示

bin目录下 nexus.vmoptions 文件,可调整内存参数,防止占用内存太大。 etc目录下 nexus-default.properties 文件可配置默认端口和host及访问根目录。

然后访问 进入首页:

7.2 初始设置

点击右上角的登录:

这里参考提示:

然后输入密码进行下一步:

匿名登录,启用还是禁用?由于启用匿名登录后,后续操作比较简单,这里我们演示禁用匿名登录的操作方式:

除了默认账号 admin,admin 具有全部权限,还有 anonymous,anonymous 作为匿名用户,只具有查看权限,但可以查看仓库并下载依赖。

完成:

7.3 Nexus Repository

nexus 默认创建了几个仓库,如下:

其中仓库 Type 类型为:

仓库名称:

其中 maven-public 相当于仓库总和,默认把其他 3 个仓库加进来一起对外提供服务了,另外,如果有自己建的仓库,也要加进该仓库才有用。

初始状态下,这几个仓库都没有内容:

7.4 创建 Nexus Repository

除了自带的仓库,有时候我们需要单独创建自己的仓库,按照默认创建的仓库类型来创建我们自己的仓库。

7.4.1 创建 Nexus 宿主仓库

点击左边导航栏中的 Repositories,如下图:

然后创建仓库,如下:

同理创建 releases 仓库,然后查看列表:

宿主仓库配置如下:

7.4.2 创建 Nexus 代理仓库

然后建一个代理仓库,用来下载和缓存中央仓库的构件,这里选择 proxy:

然后创建:

代理仓库配置中,仓库 ID、仓库名称、Provider、Policy 以及 Default Local Storage Location 等配置的含义与宿主仓库相同,不再赘述。需要注意的是,代理仓库的 Repository Type 的取值是 proxy。

代理仓库配置如下表:

7.4.3 创建 Nexus 仓库组

下面我们将创建一个仓库组,并将刚刚创建的 3 个仓库都聚合起来,这里选择 group,如下:

查看 Nexus 仓库列表,可以看到创建的仓库组已经创建完成,如下图:

7.5 通过 Nexus 下载 jar 包

由于初始状态下都没有内容,所以我们需要进行配置,我们先在本地的 Maven 的配置文件中新建一个空的本地仓库作为测试。

然后,把我们原来配置仓库地址的 mirror 标签改成下面这样:

这里的 url 标签是这么来的:

把上图中看到的地址复制出来即可。如果我们在前面允许了匿名访问,到这里就够了。但如果我们禁用了匿名访问,那么接下来我们还要继续配置 settings.xml:

注意:server 标签内的 id 标签值必须和 mirror 标签中的 id 值一样。

然后找一个用到框架的 Maven 工程,编译 compile,下载过程日志:

下载后,Nexus 服务器上就有了 jar 包:

7.6 将 jar 包部署到 Nexus

这一步的作用是将通用的模块打成 jar 包,发布到 Nexus 私服,让其他的项目来引用,以更简洁高效的方式来实现复用和管理。

需要配置 server:

然后在我们需要上传的 maven 项目中的添加如下配置:

7.6.1 上传到 maven-snapshots

执行命令 将当前 SNAPSHOT(快照版)上传到私服 maven-snapshots。

7.6.2 上传到 maven-releases

修改项目的版本,如下:

执行命令 :

查看:

package 命令完成了项目编译、单元测试、打包功能。 install 命令完成了项目编译、单元测试、打包功能,同时把打好的可执行jar包(war包或其它形式的包)布署到本地maven仓库。 deploy 命令完成了项目编译、单元测试、打包功能,同时把打好的可执行jar包(war包或其它形式的包)布署到本地maven仓库和远程maven私服仓库。

先给结论:编订依赖列表的程序员。初次设定一组依赖,因为尚未经过验证,所以确实有可能存在各种问题,需要做有针对性的调整。那么谁来做这件事呢?我们最不希望看到的就是:团队中每个程序员都需要自己去找依赖,即使是做同一个项目,每个模块也各加各的依赖,没有统一管理。那前人踩过的坑,后人还要再踩一遍。而且大家用的依赖有很多细节都不一样,版本更是五花八门,这就让事情变得更加复杂。

所以虽然初期需要根据项目开发和实际运行情况对依赖配置不断调整,最终确定一个各方面都 OK 的版本。但是一旦确定下来,放在父工程中做依赖管理,各个子模块各取所需,这样基本上就能很好的避免问题的扩散。

即使开发中遇到了新问题,也可以回到源头检查、调整 dependencyManagement 配置的列表——而不是每个模块都要改。

8.1 表现形式

由于实际开发时我们往往都会整合使用很多大型框架,所以一个项目中哪怕只是一个模块也会涉及到大量 jar 包。数以百计的 jar 包要彼此协调、精密配合才能保证程序正常运行。而规模如此庞大的 jar 包组合在一起难免会有磕磕碰碰。最关键的是由于 jar 包冲突所导致的问题非常诡异,这里我们只能罗列较为典型的问题,而没法保证穷举。

但是我们仍然能够指出一点:一般来说,由于我们自己编写代码、配置文件写错所导致的问题通常能够在异常信息中看到我们自己类的全类名或配置文件的所在路径。如果整个错误信息中完全没有我们负责的部分,全部是框架、第三方工具包里面的类报错,这往往就是 jar 包的问题所引起的。

而具体的表现形式中,主要体现为找不到类或找不到方法。

8.1.1 抛异常:找不到类

此时抛出的常见的异常类型:

我们来举个例子:

httpclient 这个 jar 包中有一个类:org.apache.http.conn.ssl.NoopHostnameVerifier。这个类在较低版本中没有,但在较高版本存在。比如:

那当我们确实需要用到 NoopHostnameVerifier 这个类,我们看到 Maven 通过依赖传递机制引入了这个 jar 包,所以没有明确地显式声明对这个 jar 包的依赖。可是 Maven 传递过来的 jar 包是 4.3.6 版本,里面没有包含我们需要的类,就会抛出异常。

而『冲突』体现在:4.3.6 和 4.4 这两个版本的 jar 包都被框架所依赖的 jar 包给传递进来了,但是假设 Maven 根据『版本仲裁』规则实际采纳的是 4.3.6。

版本仲裁 Maven 的版本仲裁机制只是在没有人为干预的情况下,自主决定 jar 包版本的一个办法。而实际上我们要使用具体的哪一个版本,还要取决于项目中的实际情况。所以在项目正常运行的情况下,jar 包版本可以由 Maven 仲裁,不必我们操心;而发生冲突时 Maven 仲裁决定的版本无法满足要求,此时就应该由程序员明确指定 jar 包版本。

版本仲裁遵循以下规则:

在下图的例子中,对模块 pro25-module-a 来说,Maven 会采纳 1.2.12 版本。

此时 Maven 采纳哪个版本,取决于在 pro29-module-x 中,对 pro30-module-y 和 pro31-module-z 两个模块的依赖哪一个先声明。

8.1.2 抛异常:找不到方法

程序找不到符合预期的方法。这种情况多见于通过反射调用方法,所以经常会导致:java.lang.NoSuchMethodError。

8.1.3 没报错但结果不对

发生这种情况比较典型的原因是:两个 jar 包中的类分别实现了同一个接口,这本来是很正常的。但是问题在于:由于没有注意命名规范,两个不同实现类恰巧是同一个名字。

具体例子是实际工作中遇到过:项目中部分模块使用 log4j 打印日志;其它模块使用 logback,编译运行都不会冲突,但是会引起日志服务降级,让你的 log 配置文件失效。比如:你指定了 error 级别输出,但是冲突就会导致 info、debug 都在输出。

8.2 本质

以上表现形式归根到底是两种基本情况导致的:

这里我们拿 netty 来举个例子,netty 是一个类似 Tomcat 的 Servlet 容器。通常我们不会直接依赖它,所以基本上都是框架传递进来的。那么当我们用到的框架很多时,就会有不同的框架用不同的坐标导入 netty。可以参照下表对比一下两组坐标:

| 截止到3.2.10.Final版本以前的坐标形式: | 从3.3.0.Final版本开始以后的坐标形式: | | :--------------------------------------------- | :------------------------------------- | | org.jboss.netty netty 3.2.10.Final | io.netty netty 3.9.2.Final |

但是偏偏这两个『不同的包』里面又有很多『全限定名相同』的类。例如:

8.3 解决办法

很多情况下常用框架之间的整合容易出现的冲突问题都有人总结过了,拿抛出的异常搜索一下基本上就可以直接找到对应的 jar 包。我们接下来要说的是通用方法。

不管具体使用的是什么工具,基本思路无非是这么两步:

8.3.1 IDEA 的 Maven Helper 插件

这个插件是 IDEA 中安装的插件,不是 Maven 插件。它能够给我们罗列出来同一个 jar 包的不同版本,以及它们的来源。但是对不同 jar 包中同名的类没有办法。

然后基于 pom.xml 的依赖冲突分析,如下:

查看冲突分析结果:

8.3.2 Maven 的 enforcer 插件

使用 Maven 的 enforcer 插件既可以检测同一个 jar 包的不同版本,又可以检测不同 jar 包中同名的类。

这里我们引入两个对 netty 的依赖,展示不同 jar 包中有同名类的情况作为例子。

然后配置 enforcer 插件:

执行如下 Maven 命令:

部分运行结果:

文章知识点与官方知识档案匹配,可进一步学习相关知识

1.pom.xml配置详解信息:

版权声明


相关文章:

  • c语言swap函数怎么调用2025-01-28 07:01:08
  • css怎么给字体添加颜色2025-01-28 07:01:08
  • 137端口关闭有什么影响2025-01-28 07:01:08
  • jstorm官网2025-01-28 07:01:08
  • amc数学竞赛什么时候出成绩2025-01-28 07:01:08
  • 微软雅黑mac字体下载2025-01-28 07:01:08
  • 树莓派3b+介绍2025-01-28 07:01:08
  • 介绍计算机的发展历程2025-01-28 07:01:08
  • pycharm 单元测试2025-01-28 07:01:08
  • sscom v5.12.1使用说明2025-01-28 07:01:08