背景

昨天帮一位同事排查了一个依赖冲突的问题。问题的现象就是在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. fatal: The remote end hung up unexpectedly

    git push 的时候出错,提示: fatal: The remote end hung up unexpectedly 遇见几次了,原因是因为文件太大,把限制放宽就好了.命令: git confi ...

  2. 从零开始教你封装自己的vue组件

    组件(component)是vue.js最强大的功能之一,它可以实现功能的复用,以及对其他逻辑的解耦.但经过一段时间的使用,我发现自己并没有在业务中发挥出组件的最大价值.相信很多刚开始使用vue的朋友 ...

  3. 2017上海QCon之旅总结(上)

    本来这个公众号的交流消息中间件相关的技术的.这周去上海参加了QCon,第一次参加这样的技术会议,感受挺多的,所以整理一下自己的一些想法接公众号和大家交流一下. 下面进入正题,从自己参加了的一些分享中挑 ...

  4. 【转】各种图(流程图,思维导图,UML,拓扑图,ER图)简介

    原文地址:各种图(流程图,思维导图,UML,拓扑图,ER图)简介 流程图 1.定义:流程图是对过程.算法.流程的一种图像表示,在技术设计.交流及商业简报等领域有广泛的应用. 2.案例 3.计算机语言只 ...

  5. LINUX 笔记-命令执行顺序 && ,||

    && 格式:命令1 && 命令2 说明:命令1返回真(即返回0,成功被执行)后,命令2才能够被执行 例:/apps/bin目录将会被移到/apps/dev/bin目录下 ...

  6. 机器学习技法:06 Support Vector Regression

    Roadmap Kernel Ridge Regression Support Vector Regression Primal Support Vector Regression Dual Summ ...

  7. [http服务]

    [http服务] CentOS 6 httpd 程序环境 记录了httpd的主进程编号: v 主程序文件: /usr/sbin/httpd /usr/sbin/httpd.worker /usr/sb ...

  8. 使用angular4和asp.net core 2 web api做个练习项目(二), 这部分都是angular

    上一篇: http://www.cnblogs.com/cgzl/p/7755801.html 完成client.service.ts: import { Injectable } from '@an ...

  9. SE6新特性之集合Set、Map、WeakSet和WeakMap详解

    SE5的时候我们经常用数组或者类数组对象来操作数据,而对于一些使用惯了java之类语言的集合的开发人员来说,总有少了点什么的感觉,SE6提供Set和Map这两个集合.不仅从根本上为一些问题提供了解决方 ...

  10. 分布式数据库TiDB的部署

    转自:https://my.oschina.net/Kenyon/blog/908370 一.环境 CentOS Linux release 7.3.1611 (Core)172.26.11.91   ...