首页 > 编程语言 >Java类全路径冲突解决方法

Java类全路径冲突解决方法

时间:2024-04-08 11:33:55浏览次数:21  
标签:4.5 Java lib BOOT 路径 jar INF 类全 httpclient

1. 问题

今天在开发中遇到这样一个问题,A同事在导入了我们的实验SDK后,发现实验无法正常获取,查看日志发现了NoClassDefFoundError异常,无法加载的的类中逻辑比较简单,只依赖了另外一个SDK包

2. NoClassDefFoundError分析和解决

一般情况下,碰到NoClassDefFoundError错误,首先我们会想到的是Maven包版本冲突了

Maven当存在多个版本的依赖时,会依赖一定的原则选取一个版本,这个版本很可能和开发环境中的版本不一致,导致一些类或者字段取不到,就会出现上面的错误

具体依赖的原则如下:

  1. 最短路径,其中A-B-C-X(1.0) , A-D-X(2.0)。由于X(2.0)路径最短,所以项目使用的是X(2.0)
  2. 顺序优先,如果A-B-X(1.0) ,A-C-X(2.0) 这样的路径长度一样怎么办呢?这样的情况下,maven会根据pom文件声明的顺序加载,如果先声明了B,后声明了C,那就最后的依赖就会是X(1.0)
  3. 覆盖优先,子pom内声明的优先于父pom中的依赖

如果出现了冲突,应该如何解决,基本是通过两种方式

  1. 排除掉不想要的版本,下面是将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>
  1. 统一版本,下面规定了此项目需要的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

发现了这个类存在于两个包中
image-20240408112400848

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

从上面的示例中可以看到

  1. 同样的代码,因为maven的顺序不同,AllowAllHostnameVerifier使用的版本也不一样,看起来是maven的优先级还是在生效
  2. 同时可以看到,两个包是可以共存的,对于不在交集中的类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,它们两个是兼容的,差别很小)

image-20240407194835146

从最短路径的原则来看呢,好像应该使用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其实是不知道的,现在我们得到下面的信息:

  1. 和maven也不是完全没有关系,因为调整顺序确实影响了使用的类
  2. 不是完全和maven jar包版本优先级规则决定

3.4 总结

在做了上面的一系列的实验之后,我还是发现了一些规律,对于相同的类名,具体使用哪个,是由jar包的顺序决定的,这里分两种情况:

  1. 如果是通过IDEA启动一个maven的java类,IDEA会根据maven的顺序来传classpath参数,使用的类必定是一个出现的jar包

image-20240407200039505

  1. 如果是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包

经过一些实验之后,可以得到的结论是:

  1. 最好的处理方法,是把冲突的包排除掉,因为大部分情况是因为代码复制改名出现的

  2. 其次,如果必须共存的话,只能依赖一个原则判断使用的类是哪个jar包中的,classpath参数的jar包的顺序、springboot生成的jar中的BOOT-INF/lib/xxx.jar顺序

  3. 如果上述的顺序不满足需要,可以调整maven中的依赖顺序来解决,可以参考这个原则

    1. 依赖在pom前面越优先
    2. 和最短路径无关
    3. 后面出现的依赖覆盖前面的依赖从而改变顺序

至于为什么jar包在前面,会优先使用其中的类,可以研究一下类加载器URLClassLoaderLaunchedURLClassLoader, 它们寻找类是从一个URL List里面遍历的,在前面的会先寻找到

参考

【1】MAVEN依赖的优先原则 - 知乎 (zhihu.com)

【2】聊一聊Springboot的类加载机制 - 简书 (jianshu.com)

标签:4.5,Java,lib,BOOT,路径,jar,INF,类全,httpclient
From: https://www.cnblogs.com/songjiyang/p/18120760

相关文章

  • Java零基础入门-String
    一、概述        近期有个小伙伴在看我的文章,说我在讲完基本类型,只是顺带提了一嘴String,然后说他的老师在课上对这String讲了足足一节课,好家伙,我觉得啊,这勾起了我的回忆,当初我看String的源码及知识点时,也是看了很久,这怪我,疏忽大意了啊。既然被这位小伙伴吐槽,那我就为......
  • Java零基础入门-多态
    一、概述        我之前上几期是教学了java类、及面向对象编程的三大基本特性,封装继承和多态。前一期,我是把继承大致讲了一遍,不知道你们对它有没有理解,何为继承,继承有啥好处,可以多继承嘛?等这类问题,我在上一期内容都有讲解,如果你们答不出来,没关系,你们可以再回去瞅瞅。......
  • 学习笔记445—白盒测试用例设计方法(语句覆盖、判定覆盖、条件覆盖、判定/条件覆盖、组
    白盒测试用例设计方法(语句覆盖、判定覆盖、条件覆盖、判定/条件覆盖、组合覆盖、路径覆盖、基本路径覆盖语句覆盖:每条语句至少执行一次。判定覆盖:每个判定的所有可能结果至少出现一次。(又称“分支覆盖”)条件覆盖:每个条件的所有可能结果至少执行一次。判定/条件覆盖:一个判定中的每......
  • java8 Lambda 异常处理
    异常接口publicclassTry{ publicstatic<T>Consumer<?superT>of(UncheckedConsumer<T>action){ Objects.requireNonNull(action); returnt->{ try{ action.accept(t); }catch(Exceptione){ thrownewRuntimeExcepti......
  • JavaScript性能优化
    JavaScript性能优化JavaScript是前端开发中最常用的脚本语言之一,优化它的性能可以有效地提升页面响应速度和流畅度。下面介绍一些常见的JavaScript性能优化方法: 1.函数节流函数节流是指在一定时间内只执行一次函数,比如鼠标滚动事件,如果没有限制,那么每次鼠标滚动都会触发大量......
  • Java 方法真人游戏网站app
    在前面几个章节中我们经常使用到 System.out.println(),那么它是什么呢?println()是一个方法。System是系统类。out是标准输出对象。这句话的用法是调用系统类System中的标准输出对象out中的方法println()。那么什么是方法呢?Java方法是语句的集合,它们在......
  • Java中null和空的区别
    null是没有地址,会报空指针异常即报错。""是有地址但是里面的内容是空的。问题一:null和""的区别......
  • 如何使用Java和RabbitMQ实现延迟队列(方式二)?
    前言昨天写了一篇关于Java和RabbitMQ使用插件实现延迟队列功能的文章,今天来讲下另外一种方式,不需要RabbitMQ的插件。前期准备,需要安装好docker、docker-compose的运行环境。需要安装RabbitMQ的可以看下面这篇文章。如何使用PHP和RabbitMQ实现消息队列?-CSDN博客使用RabbitM......
  • 如何使用Java和RabbitMQ实现延迟队列?
    前言今天我们使用Java和RabbitMQ实现消息队列的延迟功能。前期准备,需要安装好docker、docker-compose的运行环境。需要安装RabbitMQ的可以看下面这篇文章。如何使用PHP和RabbitMQ实现消息队列?-CSDN博客今天讲的是依赖RabbitMQ的延迟插件实现消息队列的延迟功能。如何安装......
  • 学习Java Day3-05 (流程控制-循环结构while,do……while,for,增强for)
    循环结构while循环do…while循环for循环在Java5中引入了一种主要用于数组的增强型for循环。while循环while是最基本的循环,它的结构为:while(布尔表达式){ //循环内容}只要布尔表达式为ture,循环就会一直执行下去。我们大多数情况是会让循环停止下来的,我们需要一......