16

好机会,我要帮女同事解决Maven冲突问题

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzIwMDY0Nzk2Mw%3D%3D&%3Bmid=2650321046&%3Bidx=1&%3Bsn=4ffd6f86c9f2f3c8b5fb9d35b5dc11ec
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

任何一个故事起因最重要

任何一个职业,女生都有绝对的优势。 更别提 IT 行业了,在部门中要是有女程序猿那肯定是香饽饽,备受呵护呀。

FRRVbqB.jpg!web

之前有一次,一位刚来的妹子遇到问题了,画风顿时就变成上面的图片了,群起而围之,但是最后的结果并不理想,还是得我出马(此处有点小吹牛)。

妹子遇到的是 Jar 包冲突的问题,错误信息是 Caused by: java.lang.ClassNotFoundException,看错误要么就是缺少某个 Jar 包,要么就是冲突了。

其实在工作中经常会遇到这种冲突的问题,比如: Caused by:java.lang.NoSuchMethodError 这个异常信息也是冲突导致的,想要解决冲突问题就必须得知道哪里冲突了(好像是废话)。

大部分都是用 Maven 来管理依赖的 Jar,今天这篇文章主要是讲解如何解决 Maven 带来的依赖冲突问题。

Maven 回顾

Maven 自述

Maven 是用于构建和管理 Java 项目的工具。 对于 Java 方向的来说,Maven 几乎都要接触和使用。 当然也有其他的工具来代替 Maven,比如 Ant 和 Gradle。

之前有接触过 Grails 构建的 Java Web 项目,就是用 Gradle 来做依赖管理的。 至于 Ant 也在刚工作的时候在一些老项目中有见到过,后面几乎没见过了。

Maven 文档地址: https://maven.apache.org [1]

使用 Maven 可以让我们快速构建一个新的项目,并且很方便的可以集成和管理多个三方的框架。 当我们需要某个框架时可以去搜索一下这个框架的信息,然后配置到你的项目中即可。

搜索地址: https://mvnrepository.com [2]

比如我们想要使用 Spring Boot,除了在 Spring 的文档中获取依赖的版本,也可以自己去搜索,选择对应的版本,如下图:

iInQvaB.jpg!web

可以看到默认就是 Maven 的依赖方式,只需要将 dependency 整段内容复制到项目的 pom.xml 文件中即可。 右侧还有很多其他的依赖方式,比如 Gradle 等。

Maven 依赖传递

今天主要讲下如何去解决 Maven 做依赖管理的时候 Jar 包冲突的问题,在解决之前先来了解下基本的知识。

muiAf2A.png!web

上图展示了 Maven 的依赖传递性,首先是项目 B 中依赖了 Spring 和 Guava 两个框架。 然后项目 A 又依赖了项目 B,所以项目 A 也会依赖 Spring 和 Guava 两个框架。

依赖传递 Jar 包选择逻辑

依赖性传递会导致项目中依赖很多其他版本的 Jar,这种情况下怎么进行 Jar 包的选择呢?

有两个规则:

  • 不同距离,距离近优先

  • 相同距离,前者优先

如下图所示,项目依赖了项目 A 和项目 B,A 和 B 分别依赖了 Guava,但是从依赖层次来看,项目 B 的层次更浅,故 Guava18.0 会被优先选择。

AJvuYvb.png!web

当距离相同的时候,就会优先选择定义在前面的,如下图所示,项目 A 和项目 B 都分别依赖了 Guava15.0 和 Guava18.0 的版本,但是项目 A 的顺序在项目 B 的前面,所以会优先选择 Guava15.0 版本。

uIvANnj.png!web

通过依赖传递性经常会导致 Jar 包冲突的问题,比如下图的项目 A 本身依赖了 Guava15.0,然后又依赖了项目 B,项目 B 中依赖了 Guava18.0,这样项目 A 就会同时依赖 Guava15.0 和 Guava18.0。

如果刚好用到了高版本不兼容低版本的方法和类时,就会出现选择错误,因为 Maven 会根据依赖树的深浅来选型浅的依赖,也就是 15.0。

ZzaEJnA.png!web

冲突案例

下面就是一个典型的 Jar 包冲突问题,当一个 Jar 有多个版本的时候,就会出现冲突。

错误信息可以看到 com.google.common.collect.FluentIterable.concat 这个方法找不到,目前是从 guava-18.0.jar 中加载的,这种问题我们改怎么解决呢?

Description:

An attempt was made to call the method com.google.common.collect.FluentIterable.concat(Ljava/lang/Iterable;Ljava/lang/Iterable;)Lcom/google/common/collect/FluentIterable; but it does not exist. Its class, com.google.common.collect.FluentIterable, is available from the following locations:

jar:file:/Users/yinjihuan/.m2/repository/com/google/guava/guava/18.0/guava-18.0.jar!/com/google/common/collect/FluentIterable.class

It was loaded from the following location:

file:/Users/yinjihuan/.m2/repository/com/google/guava/guava/18.0/guava-18.0.jar


Action:

Correct the classpath of your application so that it contains a single, compatible version of com.google.common.collect.FluentIterable

解决思路之悬丝诊脉

找出冲突的 Jar,看看当前项目中依赖了哪几个版本。

Eclipse

在 Eclipse 中可以双击 pom 文件,进入 Dependency 视图,输入你要搜索的 jar 名称进行搜索,就可以看出当前项目中哪些框架依赖了你搜索的 jar,什么版本都能知道。

E3UFRvb.jpg!web

Idea

Idea 中可以安装 maven helper 插件来查看相关依赖信息,默认选中 Conflicts 会展示当前项目存在冲突的依赖,当然我们也可以直接查看树形的依赖关系去分析冲突。

fa6neav.jpg!web

Maven 命令

不用不借助于开发工具的插件,我们可以直接用 Maven 命令来查看当前项目的依赖关系,命令行进入到你要分析的项目目录下,执行下面的命令将分析结果保存到文件中:

mvn dependency:tree > tree.log

执行完之后依赖的信息结构如下:

vANJfmN.jpg!web

搜索了下 guava,发现在 smjdbctemplate 中依赖了 18.0 版本,这个框架是我自己基于 jdbctemplate 封装的一个框架。

ZrAJFnR.jpg!web

解决思路之察言观色

其实很明显,错误信息已经告诉我们 18.0 中找不到 concat 方法,所以 18.0 肯定是不能用的,通过前面的分析,找到了直接依赖 guava.18.0.jar 的是 smjdbctemplate,解决办法就是将 smjdbctemplate 中的 guava 排除掉。

<dependency>

<groupId>com.github.yinjihuan</groupId>

<artifactId>smjdbctemplate</artifactId>

<version>1.1</version>

<exclusions>

<exclusion>

<groupId>com.google.guava</groupId>

<artifactId>guava</artifactId>

</exclusion>

</exclusions>

</dependency>

还有就是根据依赖树的深浅度来判断当前项目依赖的是哪个版本,如下图:

RBzuaiJ.jpg!web

18.0 是最浅的,肯定是依赖它,其实在 Eclipse 里面直接查看 Maven Dependencies 就可以指定当前项目依赖哪些框架和版本信息,如下图:

6F363yr.jpg!web

当我们排除掉 18.0 后再来看依赖的版本是 20.0,如下图:

JZRVB3y.jpg!web

根据依赖树的深浅度,20.0 和 19.0 都是一样的层级,但是 20.0 在 19.0 前面,所以优先选择 20.0 版本。

再来看项目中的 pom 文件,发现 swagger 的声明顺序在 apollo 的前面。

UbiQjmu.png!web

如果我们把顺序调整一下,那么就会依赖 19.0 的版本。

bQfeQ3f.jpg!web

总结

通过我仔细耐心的讲解,妹子终于自己解决了遇到的问题,后面的事你们就猜去吧。 :laughing:

这种问题其实无法避免,当你依赖的三方框架越多的时候,冲突的可能性就越大。 碰到问题的时候沉下心来仔细分析,借助于工具帮助你排查问题。

当然我们在自己项目中去依赖三方的框架,也是要注意版本的问题,特别是对于多模块的项目,每个子模块都去依赖不同的版本,这样很容易出问题,一般建议在父 pom 中 dependencyManagement 来统一管理版本,子模块直接统一使用父 pom 中定义好的版本。

还有就是可以使用 optional 来设置可选依赖,比如说你要封装一个通用的模块 Common,这个模块中有很多通用的功能,项目 A 依赖只需要使用功能 A,项目 B 依赖只需要使用功能 B。 每个功能都依赖了三方的 Jar,这个时候如果你不做任何处理,只要依赖了你这个通用的模块 Common,那么也就会间接依赖这两个功能的第三方 Jar。 这个时候可以通过设置 optional=true 来解决这个问题,我依赖了你的通用模块 Common,如果我要使用 A 功能,那么我必须显示依赖 A 功能需要的三方依赖才可以。

参考资料

[1]

maven: https://maven.apache.org/

[2]

mvnrepository: https://mvnrepository.com/

热文推荐

上线前一个小时,dubbo这个问题可把我折腾惨了

线上服务CPU使用率百分百,注册中心却看不到该服务

GitHub 消息邮件通知太烦人? 收下这份指南!

RPC接口如何进行压力测试?

jMjIJ3v.jpg!webvAjYjmU.jpg!web

UjmyInZ.jpg!web

如有收获,点个在看,诚挚感谢


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK