Java类全路径冲突解决方法
1. 问题
今天在开发中遇到这样一个问题,A同事在导入了我们的实验SDK后,发现实验无法正常获取,查看日志发现了NoClassDefFoundError异常,无法加载的的类中逻辑比较简单,只依赖了另外一个SDK包
2. NoClassDefFoundError分析和解决
一般情况下,碰到NoClassDefFoundError错误,首先我们会想到的是Maven包版本冲突了
Maven当存在多个版本的依赖时,会依赖一定的原则选取一个版本,这个版本很可能和开发环境中的版本不一致,导致一些类或者字段取不到,就会出现上面的错误
具体依赖的原则如下:
- 最短路径,其中A-B-C-X(1.0) , A-D-X(2.0)。由于X(2.0)路径最短,所以项目使用的是X(2.0)
- 顺序优先,如果A-B-X(1.0) ,A-C-X(2.0) 这样的路径长度一样怎么办呢?这样的情况下,maven会根据pom文件声明的顺序加载,如果先声明了B,后声明了C,那就最后的依赖就会是X(1.0)
- 覆盖优先,子pom内声明的优先于父pom中的依赖
如果出现了冲突,应该如何解决,基本是通过两种方式
- 排除掉不想要的版本,下面是将a:b.jar包中的xx:yy.jar排除
<dependency>
<groupId>a</groupId>
<artifactId>b</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<artifactId>xx</artifactId>
<groupId>yy</groupId>
</exclusion>
</exclusions>
</dependency>
- 统一版本,下面规定了此项目需要的xx:yy.jar包版本是2.0.0,所以别的jar包中的版本不会在参考了
<dependencyManagement>
<dependencies>
<dependency>
<groupId>xx</groupId>
<artifactId>yy</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
</dependencyManagement>
于是我们通过dependencyManagament来统一了一下相关依赖,但是问题依旧没有解决
又通过观察日志,发现了是某个类缺失了一个字段INSTANCE,最终定位到了一个类AllowAllHostnameVerifier
发现了这个类存在于两个包中

3. 相同类分析和解决
这两个类的包名和类名是一模一样的,但jar包是不一样的,所以肯定不能通过上面提到的两种方式解决,它们会并存于依赖中
题外话,之所以会存在这样的jar包,是因为公司内部其他组的同事将中央仓库的包clone下来,重新命名上传到公司的仓库,这种通过复制代码然后改包名的方式提交jar包曾经见过两次,每次都是极难排查,非常不建议这样做!
如果真的碰到了这种情况,最好的方式是把其中一个给排除掉
但两个包都需要保留,因为可能每个包都有一些交集之外的类用到了,该如何解决呢?
3.1 通过Maven的顺序解决
<dependencies>
<dependency>
<groupId>httpclient</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
</dependencies>
public class ClassConflictTest {
public static void main(String[] args) {
ClassLoader classLoader = ClassConflictTest.class.getClassLoader();
URL resource = classLoader.getResource("org/apache/http/conn/ssl/AllowAllHostnameVerifier.class");
System.out.println(resource);
resource = classLoader.getResource("org/apache/http/impl/cookie/RFC6265StrictSpec.class");
System.out.println(resource);
}
}
//jar:file:/Users/a58/.m2/repository/httpclient/httpclient/4.3.2/httpclient-4.3.2.jar!/org/apache/http/conn/ssl/AllowAllHostnameVerifier.class
//jar:file:/Users/a58/.m2/repository/org/apache/httpcomponents/httpclient/4.5.13/httpclient-4.5.13.jar!/org/apache/http/impl/cookie/RFC6265StrictSpec.class
<dependencies>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>httpclient</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.2</version>
</dependency>
</dependencies>
//jar:file:/Users/a58/.m2/repository/httpclient/httpclient/4.3.2/httpclient-4.3.2.jar!/org/apache/http/conn/ssl/AllowAllHostnameVerifier.class
//jar:file:/Users/a58/.m2/repository/org/apache/httpcomponents/httpclient/4.5.13/httpclient-4.5.13.jar!/org/apache/http/impl/cookie/RFC6265StrictSpec.class
从上面的示例中可以看到
- 同样的代码,因为maven的顺序不同,
AllowAllHostnameVerifier使用的版本也不一样,看起来是maven的优先级还是在生效 - 同时可以看到,两个包是可以共存的,对于不在交集中的类
RFC6265StrictSpec,还是会找到
3.2 最短路径不生效
如果pom中这样写
<dependency>
<groupId>xxxxx</groupId>
<artifactId>exp-client</artifactId>
<version>1.4.4</version>
<exclusions>
<exclusion>
<artifactId>spring-expression</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>httpclient</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.2</version>
</dependency>
依赖如下:(注意 : exp包用的httpclient是4.5.1,而我测试用的包是4.5.13,它们两个是兼容的,差别很小)

从最短路径的原则来看呢,好像应该使用4.3.2的类,但输出使用的是4.5.1的类,如下:
jar:file:/Users/a58/.m2/repository/org/apache/httpcomponents/httpclient/4.5.1/httpclient-4.5.1.jar!/org/apache/http/conn/ssl/AllowAllHostnameVerifier.class
jar:file:/Users/a58/.m2/repository/org/apache/httpcomponents/httpclient/4.5.1/httpclient-4.5.1.jar!/org/apache/http/impl/cookie/RFC6265StrictSpec.class
3.3 分析
其实根据maven规则来判断使用哪个类,本身就有些奇怪,因为maven主要是编译阶段的任务,把我们的依赖jar打包好,代码编译好,那运行时期选择用哪个类,maven其实是不知道的,现在我们得到下面的信息:
- 和maven也不是完全没有关系,因为调整顺序确实影响了使用的类
- 不是完全和maven jar包版本优先级规则决定
3.4 总结
在做了上面的一系列的实验之后,我还是发现了一些规律,对于相同的类名,具体使用哪个,是由jar包的顺序决定的,这里分两种情况:
- 如果是通过IDEA启动一个maven的java类,IDEA会根据maven的顺序来传classpath参数,使用的类必定是一个出现的jar包

如果是springboot项目,maven plugin插件也会根据maven的顺序决定jar包出现的顺序,使用的类也必定是排在前面的jar包
// 情况1
94 BOOT-INF/lib/
95 BOOT-INF/lib/httpclient-4.3.2.jar
96 BOOT-INF/lib/httpclient-4.5.13.jar
97 BOOT-INF/lib/httpcore-4.4.13.jar
98 BOOT-INF/lib/commons-logging-1.2.jar
99 BOOT-INF/lib/commons-codec-1.11.jar
100 BOOT-INF/lib/spring-boot-2.7.1.jar
101 BOOT-INF/lib/spring-context-5.3.21.jar
102 BOOT-INF/lib/spring-aop-5.3.21.jar //情况2
94 BOOT-INF/lib/
95 BOOT-INF/lib/httpclient-4.5.13.jar
96 BOOT-INF/lib/httpcore-4.4.13.jar
97 BOOT-INF/lib/commons-logging-1.2.jar
98 BOOT-INF/lib/commons-codec-1.11.jar
99 BOOT-INF/lib/httpclient-4.3.2.jar
100 BOOT-INF/lib/spring-boot-2.7.1.jar
101 BOOT-INF/lib/spring-context-5.3.21.jar
这个顺序一般是pom文件中jar依赖的顺序,因为解析某个jar的时候,同时会把它依赖的jar也解析,所以非最短路径也比较最短路优先,正如最短路径不优先例子中springboot jar包中的顺序如下
137 BOOT-INF/lib/swagger-annotations-1.5.20.jar
138 BOOT-INF/lib/swagger-models-1.5.20.jar
139 BOOT-INF/lib/mapstruct-1.3.1.Final.jar
140 BOOT-INF/lib/com.bj58.spat.wos.client-1.0.17.jar
141 BOOT-INF/lib/httpclient-4.5.1.jar
142 BOOT-INF/lib/commons-logging-1.2.jar
143 BOOT-INF/lib/httpcore-4.4.3.jar
144 BOOT-INF/lib/httpmime-4.5.1.jar
145 BOOT-INF/lib/json-20140107.jar
146 BOOT-INF/lib/commons-codec-1.9.jar
147 BOOT-INF/lib/junit-4.12.jar
148 BOOT-INF/lib/hamcrest-core-1.3.jar
149 BOOT-INF/lib/guava-31.0.1-jre.jar
150 BOOT-INF/lib/failureaccess-1.0.1.jar
151 BOOT-INF/lib/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar
152 BOOT-INF/lib/jsr305-3.0.2.jar
153 BOOT-INF/lib/checker-qual-3.12.0.jar
154 BOOT-INF/lib/error_prone_annotations-2.7.1.jar
155 BOOT-INF/lib/j2objc-annotations-1.3.jar
156 BOOT-INF/lib/com.bj58.zhaopin.zhuzhan.litecore-1.0.18.jar
157 BOOT-INF/lib/slf4j-api-1.7.25.jar
158 BOOT-INF/lib/httpclient-4.3.2.jar
但我稍微改一下pom,就会发现原先在前面出现的jar包又跑到后面去了,所以存在一些覆盖的问题
<dependency>
<groupId>xxxxx</groupId>
<artifactId>exp-client</artifactId>
<version>1.4.4</version>
<exclusions>
<exclusion>
<artifactId>spring-expression</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>httpclient</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.1</version>
</dependency>
139 BOOT-INF/lib/mapstruct-1.3.1.Final.jar
140 BOOT-INF/lib/com.bj58.spat.wos.client-1.0.17.jar
141 BOOT-INF/lib/httpmime-4.5.1.jar
142 BOOT-INF/lib/json-20140107.jar
143 BOOT-INF/lib/junit-4.12.jar
144 BOOT-INF/lib/hamcrest-core-1.3.jar
145 BOOT-INF/lib/guava-31.0.1-jre.jar
146 BOOT-INF/lib/failureaccess-1.0.1.jar
147 BOOT-INF/lib/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar
148 BOOT-INF/lib/jsr305-3.0.2.jar
149 BOOT-INF/lib/checker-qual-3.12.0.jar
150 BOOT-INF/lib/error_prone_annotations-2.7.1.jar
151 BOOT-INF/lib/j2objc-annotations-1.3.jar
152 BOOT-INF/lib/com.bj58.zhaopin.zhuzhan.litecore-1.0.18.jar
153 BOOT-INF/lib/slf4j-api-1.7.25.jar
154 BOOT-INF/lib/httpclient-4.3.2.jar
155 BOOT-INF/lib/httpclient-4.5.1.jar
156 BOOT-INF/lib/httpcore-4.4.3.jar
4. 总结
对于这种存在相同类路径的不同jar包
经过一些实验之后,可以得到的结论是:
最好的处理方法,是把冲突的包排除掉,因为大部分情况是因为代码复制改名出现的
其次,如果必须共存的话,只能依赖一个原则判断使用的类是哪个jar包中的,classpath参数的jar包的顺序、springboot生成的jar中的BOOT-INF/lib/xxx.jar顺序
如果上述的顺序不满足需要,可以调整maven中的依赖顺序来解决,可以参考这个原则
- 依赖在pom前面越优先
- 和最短路径无关
- 后面出现的依赖覆盖前面的依赖从而改变顺序
至于为什么jar包在前面,会优先使用其中的类,可以研究一下类加载器URLClassLoader和LaunchedURLClassLoader, 它们寻找类是从一个URL List里面遍历的,在前面的会先寻找到
参考
【1】MAVEN依赖的优先原则 - 知乎 (zhihu.com)
【2】聊一聊Springboot的类加载机制 - 简书 (jianshu.com)
Java类全路径冲突解决方法的更多相关文章
- paip兼容windows与linux的java类根目录路径的方法
paip兼容windows与linux的java类根目录路径的方法 1.只有 pathx.class.getResource("")或者pathx.class.getResourc ...
- Java ConcurrentModificationException异常原因和解决方法
Java ConcurrentModificationException异常原因和解决方法 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.u ...
- Java并发编程:Java ConcurrentModificationException异常原因和解决方法
Java ConcurrentModificationException异常原因和解决方法 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.u ...
- 【转】Java ConcurrentModificationException异常原因和解决方法
原文网址:http://www.cnblogs.com/dolphin0520/p/3933551.html Java ConcurrentModificationException异常原因和解决方法 ...
- 9、Java ConcurrentModificationException异常原因和解决方法
Java ConcurrentModificationException异常原因和解决方法 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.u ...
- JAVA常见中文问题的解决方法(转)
JAVA常见中文问题的解决方法 http://www.java-cn.com/club/article-5876-1.html 以下解决方案是笔者在日常生活中遇到的,希望能对你解决JAVA中文问题有所 ...
- IIS上虚拟站点的web.config与主站点的web.config冲突解决方法 分类: ASP.NET 2015-06-15 14:07 60人阅读 评论(0) 收藏
IIS上在主站点下搭建虚拟目录后,子站点中的<system.web>节点与主站点的<system.web>冲突解决方法: 在主站点的<system.web>上一级添 ...
- IIS上虚拟目录下站点的web.config与根站点的web.config冲突解决方法
IIS7.5上在站点下部署虚拟目录,访问虚拟目录下的项目提示与父节点配置冲突.,节点与的<system.web>节点与主站点的<system.web>冲突解决方法: 在站点下的 ...
- eWebeditor编辑器上传图片路径错误解决方法[疑难杂症]【转,作者:unvs】
做了一个多版本的网站,后台用的编辑器是eWebeditor,NET版,后面发现上传图片或者文件之后,路径错误无法显示,必须手工修改才行.. 为了更清楚的说明问题,我下面会说的比较详细,首先是网站文件框 ...
- Linux 下shell显示-bash-4.1$不显示用户名路径的解决方法
Linux CentOS下shell显示-bash-4.1$不显示用户名路径的解决方法 问题描述: CentOS下新增一个用户,登录进去之后shell脚本的信息如下: 而不是我们经常看 ...
随机推荐
- M3U8下载器加嗅探浏览器
M3U8下载器太多了,随便一抓一大把,没什么新奇的. 下载地址: https://www.zhaimaojun.cn/P/%e8%a7%86%e9%a2%91%e7%bd%91%e7%ab%99%e5 ...
- 用 Certbot-auto 在 letsencrypt.org申请免费 SSL 证书实现 HTTPS
参考帖子 https://www.cnblogs.com/lzpong/p/6433189.html https://www.cnblogs.com/756623607-zhang/p/1163850 ...
- 手机自适应的单位rem,与自适应网页的区别
一个网站有的会分为pc站和移动站,有的网站只有pc站,而现在更多的是自适应的站点.现在针对自适应的网页设计有很多模板,如bootstrap,它会让你轻松定制一个只适应网站,而现在大多数的网站并不是靠程 ...
- C 语言编程 — 变量与常量
目录 文章目录 目录 前文列表 变量与常量 变量 变量的类型 变量的声明 变量的定义 变量的初始化与赋值 常量 整型常量 浮点型常量 字符型场景 字符串常量 符号常量 作用域 存储类 auto 修饰符 ...
- docker镜像仓库管理Harbor
一 部署Harbor 前提: Harbor需要运行在docker上面,所以首先需要在harbor部署机器上面自行部署docker和docker-compose docker-compose安装命令如下 ...
- 京东面试:如何进行JVM调优?
JVM 调优是一个很大的话题,在回答"如何进行 JVM 调优?"之前,首先我们要回答一个更为关键的问题,那就是,我们为什么要进行 JVM 调优? 只有知道了为什么要进行 JVM 调 ...
- 记录一次配置iis与Sqlserver的集成windows账户登录
1.连接字符串,要写 "Data Source=[主机名称];Initial Catalog=[数据库名称];Integrated Security=True" 2.iis下配 ...
- JavaScript 中 toString 的奇妙使用
JavaScript 中的toString()方法,我们通常会一些其他类型的变量,转为字符串类型.但这里还有一些其他奇妙的用法. 不同的类型调用 toString() 会得到不同的结果.我们来一一分析 ...
- Asp-Net-Core学习笔记:身份认证入门 _
前言 过年前我又来更新了~ 我就说了最近不是在偷懒吧,其实这段时间还是有积累一些东西的,不过还没去整理-- 所以只能发以前没写完的一些笔记出来 就当做是温习一下啦 PS:之前说的红包封面我还没搞,得抓 ...
- Kubernetes1.16安装[kubadm方式]
Kubernetes 安装手册(非高可用版) 集群信息 1. 节点规划 部署k8s集群的节点按照用途可以划分为如下2类角色: master:集群的master节点,集群的初始化节点,基础配置不低于2C ...