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脚本的信息如下: 而不是我们经常看 ...
随机推荐
- 01. Linux 如何安装rvm和ruby
参考: https://blog.csdn.net/qq_35641923/article/details/86493822 https://www.runoob.com/ruby/ruby-inst ...
- nginx与location规则
========================================================================= 2018年3月28日 记录: location = ...
- link标签的media属性
media属性表示被链接的文档将显示在什么设备上.比如下面的例子: <head> <link rel="stylesheet" type="text/c ...
- ansible系列(24)--ansible的loop循环语句
目录 1. loop循环语句 1.1 使用循环批量安装软件 1.2 使用循环批量启动服务 1.3 使用循环批量创建用户 1.4 使用循环批量拷贝文件 1. loop循环语句 在写 playbook 的 ...
- istio sidecar 工作方式
istio 是什么 Istio 是一个开放源代码的服务网格,它为基于微服务的应用程序提供了一种统一的方式来连接.保护.监控和管理服务.Istio 主要解决的是在微服务架构中的服务间通信的复杂性问题,它 ...
- nginx 常见配置案例参考(优化)
在NGINX中,可以通过配置文件和特定的指令来实现权限控制.以下是一些常见的权限控制方法: 使用deny指令: 在NGINX配置文件中,可以使用deny指令来拒绝特定IP地址或IP地址范围的访问.可以 ...
- Linux系统中如何查看磁盘情况
Linux不像windows系统那样方便的图形界面,特别是作为服务器使用的时候,只有命令行可以使用. 我有个云服务器平时用来做一些数据分享用的,最近想看看磁盘和其中文件的占用情况,于是搜索并学习了一些 ...
- 音视频学习-exceeded mem limit: ActiveHard 50 MB (fatal)
一.现象 ReplayKit2 适配中 UPLOAD进程被系统杀掉 日志中显示原因:exceeded mem limit: ActiveHard 50 MB (fatal) 二.内存占用分析 1)系统 ...
- C#笔记 关于采集卡
周更!节日快乐! 1. 参数 1.1 CAM file CAM file是文件扩展名为.cam的可读ASCII文件,包含了参数列表,比如:AcquisitionMode,TrigMode等.通过McS ...
- 代码审计——基础(JAVAEE)
JAVAEE 目录 JAVAEE 常见框架 Struct2(控制层) Hibernate(持久层(与数据库交互)(不用再写简单的sql语句,但是需要一些列复杂的配置文件))(全ORM模型) Sprin ...