背景

昨天帮一位同事排查了一个依赖冲突的问题。问题的现象就是在IntelliJ IDEA运行项目正常,但是打包(Maven assembly jar)之后传到服务器运行失败,报错:Caused by: java.lang.NoSuchFieldError: INSTANCE

后来定位到某个类存在多个版本,其中一个版本是没有INSTANCE的。进一步发现项目所依赖的其他module,都是以assembly jar的形式install到本地仓库的,最终通过修改pom文件,对所依赖的module重新install,使其安装到本地仓库的是原始的、不包含依赖的jar。至此,问题解决。

在排查的过程中,发现了一些有趣的现象,后来又自己研究了下,现把结果记录下来,以供分享。

两种分析依赖的方式

这里先介绍两种依赖分析的方式。

Maven支持打印当前项目的依赖树,命令是mvn dependency:tree。下面是一个demo:

[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ m-c ---
[INFO] com.heyikan.demo:m-c:jar:1.0-SNAPSHOT
[INFO] \- com.heyikan.demo:m-b:jar:1.0-SNAPSHOT:compile
[INFO] \- com.heyikan.demo:m-a:jar:1.0-SNAPSHOT:compile

从这里可以看到,m-c依赖于m-b,m-b依赖于m-a。

需要注意这个命令是基于仓库进行依赖解析的,也就是说如果要解析m-c项目的依赖,那么必须确保它所依赖的所有项目都可以在仓库中找到。即使m-c和m-b是同一个项目的不同module,如果m-b没有安装到本地仓库,这个命令也会失败。

另外,这个命令打印出来的是最终的依赖结果:对于同一个项目的多个版本只会打印被选择的那一个。

另一种方式是IntelliJ IDEA的功能,支持以图形的方式展示项目的依赖图谱。打开项目的pom文件,使用快捷键Ctrl+Shift+Alt+U打开当前的图谱:

在图谱页面使用Ctrl+F快捷键,可以搜索指定的依赖。

这个命令不要求所有的依赖都已安装在本地仓库,对于同一个项目的不同module之间的依赖,可以直接解析

而且同一个依赖的不同版本依赖都会在图谱中展示出来,其中被选择的那个是黑线相连,其他的是红线。选中其中一个,会出现被弃用的依赖版本到最终选择的版本的一条连线。

注意这两者的区别,它表示使用Maven命令处理Maven项目的方式,和使用IDEA工具直接处理Maven项目的方式是有差别的。这种差别一般都会很微妙,并且是造成开发环境运行正常,但是服务器上运行失败的可能原因。

Maven依赖调解机制

下面的内容参考自许晓斌的《Maven实战》。

因为Maven的传递依赖,很可能导致依赖的冲突,这种冲突的具体形式表现在同一个项目的不同版本都出现在项目的依赖图谱中。

针对这种情况,Maven有依赖调解的规则。

首先是路径最近者优先,举例来说,如果项目A存在这样的依赖关系:A -> B -> C -> X(1.0) 和 A -> D -> X(2.0)。项目X有两个版本的依赖出现,此时因为X(1.0)的依赖长度为3,X(2.0)的依赖长度为2,最终被采用的依赖时2.0版本的X。

当第一个规则无法区别同一个依赖项目的不同版本时,使用第二个规则:位置靠前的优先。比如A -> B -> Y(1.0)和A -> C -> Y(2.0),最终会选择Y(1.0)。

有趣的是,如果直接在项目中声明一个项目的两个版本的依赖,如A -> Z(1.0)和A -> Z(2.0),则最后的会覆盖前面的。

shade插件对依赖的影响

shade插件用于制造一个包含依赖的assembly jar包。

默认的情况下,shade插件的打包包名会占用Maven原生的打包包名,如果将插件打包目标绑定到生命周期的package阶段,那么install阶段安装到本地仓库的实际上是shade插件打出来的assembly jar。

而且,这个assembly jar的pom文件已经改变,pom文件中不包含任何的依赖,因为所有的依赖都已经在它里面了。

比如说,我创建一个项目demo-dependency,它有三个模块m-am-bm-c,依赖关系为m-c -> m-b -> m-a。其中m-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">
<parent>
<artifactId>demo-dependency</artifactId>
<groupId>com.heyikan.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion> <artifactId>m-b</artifactId> <dependencies>
<dependency>
<groupId>com.heyikan.demo</groupId>
<artifactId>m-a</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build> </project>

注意,它使用了shade插件,并将打包目标shade绑定到了package阶段。

执行mvn clean install之后,去本地仓库看下这个项目的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/maven-v4_0_0.xsd">
<parent>
<artifactId>demo-dependency</artifactId>
<groupId>com.heyikan.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>m-b</artifactId>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

注意,已经没有依赖的内容了。

此时,使用mvn dependency:tree分析m-c的命令,结果如下:

--- maven-dependency-plugin:2.8:tree (default-cli) @ m-c ---
[INFO] com.heyikan.demo:m-c:jar:1.0-SNAPSHOT
[INFO] \- com.heyikan.demo:m-b:jar:1.0-SNAPSHOT:compile

试想一下,m-c项目依赖于m-b,而m-b是一个assembly jar,那么所有m-b依赖的项目最终都会被视做m-b本身。如果你想把m-c也打成一个assembly jar,如何处理m-b的依赖和其他依赖链上的冲突?恐怕无法得到什么保证。

shade插件的这种行为,实际上干扰了Maven的依赖调解机制。

要规避这个问题,最简单的方式是为shade插件的打包结果自定义名称,避免和Maven标准包名冲突:

<?xml version="1.0" encoding="UTF-8"?>
<project ...>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>${project.build.finalName}-assembly</finalName>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

注意,configuration -> finalName配置了自定义的jar包名称。

扩展阅读

  1. Java项目打包方式分析
  2. Maven核心知识

Maven依赖分析的更多相关文章

  1. Maven依赖版本冲突的分析及解决小结

    1:前言 做软件开发这几年遇到了许多的问题,也总结了一些问题的解决之道,之后慢慢的再遇到的都是一些重复性的问题了,当然,还有一些自己没有完全弄明白的问题.如果做的事情是重复的,遇到重复性问题的概率也就 ...

  2. Maven依赖解析

    本文将记录Maven工程中依赖解析机制,内容包括: Maven依赖基本结构 从仓库解析依赖的机制 依赖传递性解析实例 1. Maven依赖基本结构 上篇文章记录了Maven依赖的聚合与继承,POM中依 ...

  3. Maven依赖机制

    案例分析 让我们看一个案例研究,以了解它是如何工作的.假设你想使用 Log4j 作为项目的日志.这里你要做什么? 1.在传统方式 访问 http://logging.apache.org/log4j/ ...

  4. Maven学习(八)-----Maven依赖机制

    Maven依赖机制 在 Maven 依赖机制的帮助下自动下载所有必需的依赖库,并保持版本升级. 案例分析 让我们看一个案例研究,以了解它是如何工作的.假设你想使用 Log4j 作为项目的日志.这里你要 ...

  5. maven 学习---Maven依赖机制

    在 Maven 依赖机制的帮助下自动下载所有必需的依赖库,并保持版本升级. 案例分析 让我们看一个案例研究,以了解它是如何工作的.假设你想使用 Log4j 作为项目的日志.这里你要做什么? 1.在传统 ...

  6. Maven 依赖树的解析规则

    对于 Java 开发工程师来说,Maven 是依赖管理和代码构建的标准.遵循「约定大于配置」理念.Maven 是 Java 开发工程师日常使用的工具,本篇文章简要介绍一下 Maven 的依赖树解析. ...

  7. SpringCloud升级之路2020.0.x版-4.maven依赖回顾以及项目框架结构

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 我们先来回顾下 m ...

  8. 解决maven依赖冲突,这篇就够了!

    一.前言 什么是依赖冲突 依赖冲突是指项目依赖的某一个jar包,有多个不同的版本,因而造成了包版本冲突. 依赖冲突的原因 我们在maven项目的pom中 一般会引用许许多多的dependency.例如 ...

  9. AMD and CMD are dead之KMDjs内核之依赖分析

    有人说js中有三座大三:this.原型链和scope tree,搞懂了他们就算是js成人礼.当然还有其他不同看法的js成人礼,如熟悉js的:OOP.AP.FP.DOP.AOP.当然还听说一种最牛B的j ...

随机推荐

  1. 【特效】hover向上翻转效果

    前段时间做了很多有关css3动画的项目.虽然当时都较圆满的完成了,但事后还是要总结一下的,趁着近期工作不忙,系统的学习了一些动画理论,重点看了transform3D变换,学习了translate平移. ...

  2. WebApi系列~HttpClient的性能隐患

    回到目录 最近在进行开发过程中,基于都是接口开发,A站接口访问B接口接口来请求数据,而在这个过程中我们使用的是HttpClient这个框架,当然也是微软自己的框架,性能当前没有问题,但如果你直接使用官 ...

  3. 张高兴的 Windows 10 IoT 开发笔记:使用 ULN2003A 控制步进电机

    GitHub:https://github.com/ZhangGaoxing/windows-iot-demo/tree/master/ULN2003A

  4. Akka(29): Http:Server-Side-Api,Low-Level-Api

    Akka-http针对Connection的两头都提供了方便编程的Api,分别是Server-Side-Api和Client-Side-Api.通过这两个Api可以大大提高编程效率.当然,上期我们提到 ...

  5. log4j 和slf4j的比较

    log4j 和slf4j的比较 slf4j 官网:https://www.slf4j.org/manual.html slf4j(simple logging facade for java)是Jav ...

  6. C#基本功之泛型

    一.没有泛型之前 在没有泛型之前,我们是怎么处理不同类型的相同操作的: 示例1 //下面是一个处理string类型的集合类型 public class MyStringList { string[] ...

  7. LeetCode 605. Can Place Flowers (可以种花)

    Suppose you have a long flowerbed in which some of the plots are planted and some are not. However, ...

  8. LeetCode 78. Subsets(子集合)

    Given a set of distinct integers, nums, return all possible subsets. Note: The solution set must not ...

  9. LeetCode 53. Maximum Subarray(最大的子数组)

    Find the contiguous subarray within an array (containing at least one number) which has the largest ...

  10. python 文件相关知识

    字符编码相关 什么是字符编码 字符编码的类型 字符编码的使用 python2和python里字符编码的区别 文件的相关 文件的基础操作 打开文件的模式 字符编码 什么是字符编码在计算机里只识别二进制, ...