好机会,我要帮女同事解决Maven冲突问题
https://mp.weixin.qq.com/s/swqB37wSv4kdYcHSQwbsHg
好机会,我要帮女同事解决Maven冲突问题
任何一个故事起因最重要
任何一个职业,女生都有绝对的优势。更别提 IT 行业了,在部门中要是有女程序猿那肯定是香饽饽,备受呵护呀。
之前有一次,一位刚来的妹子遇到问题了,画风顿时就变成上面的图片了,群起而围之,但是最后的结果并不理想,还是得我出马(此处有点小吹牛)。
妹子遇到的是 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 的文档中获取依赖的版本,也可以自己去搜索,选择对应的版本,如下图:
可以看到默认就是 Maven 的依赖方式,只需要将 dependency 整段内容复制到项目的 pom.xml 文件中即可。右侧还有很多其他的依赖方式,比如 Gradle 等。
Maven 依赖传递
今天主要讲下如何去解决 Maven 做依赖管理的时候 Jar 包冲突的问题,在解决之前先来了解下基本的知识。
上图展示了 Maven 的依赖传递性,首先是项目 B 中依赖了 Spring 和 Guava 两个框架。然后项目 A 又依赖了项目 B,所以项目 A 也会依赖 Spring 和 Guava 两个框架。
依赖传递 Jar 包选择逻辑
依赖性传递会导致项目中依赖很多其他版本的 Jar,这种情况下怎么进行 Jar 包的选择呢?
有两个规则:
-
不同距离,距离近优先
-
相同距离,前者优先
如下图所示,项目依赖了项目 A 和项目 B,A 和 B 分别依赖了 Guava,但是从依赖层次来看,项目 B 的层次更浅,故 Guava18.0 会被优先选择。
当距离相同的时候,就会优先选择定义在前面的,如下图所示,项目 A 和项目 B 都分别依赖了 Guava15.0 和 Guava18.0 的版本,但是项目 A 的顺序在项目 B 的前面,所以会优先选择 Guava15.0 版本。
通过依赖传递性经常会导致 Jar 包冲突的问题,比如下图的项目 A 本身依赖了 Guava15.0,然后又依赖了项目 B,项目 B 中依赖了 Guava18.0,这样项目 A 就会同时依赖 Guava15.0 和 Guava18.0。
如果刚好用到了高版本不兼容低版本的方法和类时,就会出现选择错误,因为 Maven 会根据依赖树的深浅来选型浅的依赖,也就是 15.0。
冲突案例
下面就是一个典型的 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,什么版本都能知道。
Idea
Idea 中可以安装 maven helper 插件来查看相关依赖信息,默认选中 Conflicts 会展示当前项目存在冲突的依赖,当然我们也可以直接查看树形的依赖关系去分析冲突。
Maven 命令
不用不借助于开发工具的插件,我们可以直接用 Maven 命令来查看当前项目的依赖关系,命令行进入到你要分析的项目目录下,执行下面的命令将分析结果保存到文件中:
mvn dependency:tree > tree.log
执行完之后依赖的信息结构如下:
搜索了下 guava,发现在 smjdbctemplate 中依赖了 18.0 版本,这个框架是我自己基于 jdbctemplate 封装的一个框架。
解决思路之察言观色
其实很明显,错误信息已经告诉我们 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>
还有就是根据依赖树的深浅度来判断当前项目依赖的是哪个版本,如下图:
18.0 是最浅的,肯定是依赖它,其实在 Eclipse 里面直接查看 Maven Dependencies 就可以指定当前项目依赖哪些框架和版本信息,如下图:
当我们排除掉 18.0 后再来看依赖的版本是 20.0,如下图:
根据依赖树的深浅度,20.0 和 19.0 都是一样的层级,但是 20.0 在 19.0 前面,所以优先选择 20.0 版本。
再来看项目中的 pom 文件,发现 swagger 的声明顺序在 apollo 的前面。
如果我们把顺序调整一下,那么就会依赖 19.0 的版本。
总结
通过我仔细耐心的讲解,妹子终于自己解决了遇到的问题,后面的事你们就猜去吧。😆
这种问题其实无法避免,当你依赖的三方框架越多的时候,冲突的可能性就越大。碰到问题的时候沉下心来仔细分析,借助于工具帮助你排查问题。
当然我们在自己项目中去依赖三方的框架,也是要注意版本的问题,特别是对于多模块的项目,每个子模块都去依赖不同的版本,这样很容易出问题,一般建议在父 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/
=========================================================================
=========================================================================
=========================================================================
Maven实战-maven中的可选依赖(optional)_葵续浅笑的博客-CSDN博客
https://blog.csdn.net/lovejj1994/article/details/80283240
Maven实战-maven中的可选依赖(optional)
在一些项目中,我们知道用exclusion排除一些依赖包,这属于依赖排除(Dependency Exclusions),还有一种就是今天所说的可选依赖(Optional Dependencies)。主要还是讲怎么用。
准备工作
准备两个工程,简单点,就是A和B
只看POM文件,这是A的pom文件。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>A</groupId>
<artifactId>A</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.9</version>
</dependency>
</dependencies>
</project>
这是B的pom文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>B</groupId>
<artifactId>B</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>A</groupId>
<artifactId>A</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
在这种情况下,joda-time包在B工程会被正常引用。
加入optional
在A工程对joda-time添加optional选项,这时在B工程中,joda-time依赖包会消失.
<dependencies>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.9</version>
<optional>true</optional>
</dependency>
</dependencies>
如果想引这个包,需要在A项目中设optional为false或者去除optional,或者在B项目中显式调用。
parent 继承的情况
如果A的pom像下面这样配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>A</groupId>
<artifactId>A</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.9</version>
<optional>true</optional>
</dependency>
</dependencies>
</dependencyManagement>
</project>
B再去引用的话,还是可以正常引用joda-time包,optional选项在统一控制版本的情况下会失效。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>B</groupId>
<artifactId>B</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>A</groupId>
<artifactId>A</artifactId>
<version>1.0-SNAPSHOT</version>
</parent> <dependencies>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
</dependencies>
</project>