借用Arthas分析maven非包版本冲突导致找不到类问题

x33g5p2x  于2021-09-19 转载在 Java  
字(3.3k)|赞(0)|评价(0)|浏览(426)

 
背景:

  1. 微服务应用xxx.jar依赖netty-all-4.1.25Final,项目中使用通信工具async-http-client-2.0.31内置耦合了io.netty的几个同包名同类名的类;
  2. Tomcat应用运行时报:java.lang.NoSuchMethodError: io.netty.channel.DefaultChannelId.newInstance()Lio/netty/channel/DefaultChannelId;。But netty-all包中是存在DefaultChannelId类的,显然发生冲突了(该类加载的地方并非来自于netty-all-4.1.25Final.jar==>后面分析类来源)
  3. 微服务应用必须使用高版本的netty,固只能考虑升级http-client,很遗憾,http-client从某个版本开始结构发生了变化,虽然已解耦了io.netty,But某些类已经被删除了;
  4. 两种方案:1)重构项目中使用的http-client,升级到高版本进行重构;2)保持netty-all-4.1.25Final不变的同时让JVM加载到想要的类;

 

一、问题分析过程
  1. 问题:

     pom.xml文件依赖:

······

    <dependency>
        <groupId>org.asynchttpclient</groupId>
        <artifactId>async-http-client</artifactId>
        <version>2.0.31</version>
    </dependency>

    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.25.Final</version>
    </dependency>

    ······

启动应用调用某依赖到netty-all.jar的服务接口时报错:
java.lang.NoSuchMethodError: io.netty.channel.DefaultChannelId.newInstance()Lio/netty/channel/DefaultChannelId

附上http-client工具包是如何嵌入netty相关包的:

httpclient并没有把依赖netty的部分当做依赖引入,而是拷贝过来内嵌在自己的工具包里面。关键是同package名同类名<exclude>显然做不到,哪个依赖放在前面显然是无效的,因为依赖的加载受Tomcat版本、操作系统的文件系统对文件的排列等因素的影响

 
2. 解决过程:

目的:让JVM加载到netty-all下的DefaultChannelId.class而不是http-client下的DefaultChannelId.class
*
maven出现同个jar包不同版本时,会遵从路径最短第一声明原则决定加载哪个包;对不同jar中存在同package名同className的情况时Maven将束手无策,取决于先加载哪个jar就先加载到那个class,另外一个jar的相同class将不会加载;
*
Tomcat服务下JVM应用是如何加载jarclass的:

加载顺序:

1. $java_home/lib 目录下的java核心api
2. $java_home/lib/ext 目录下的java扩展jar包
3. java -classpath/-Djava.class.path所指的目录下的类与jar包
4. $CATALINA_HOME/common目录下按照文件夹的顺序从上往下依次加载
5. $CATALINA_HOME/server目录下按照文件夹的顺序从上往下依次加载
6. $CATALINA_BASE/shared目录下按照文件夹的顺序从上往下依次加载
7. 我们的项目路径/WEB-INF/classes下的class文件
8. 我们的项目路径/WEB-INF/lib下的jar文件

在同一个文件夹下,jar包是按顺序从上到下依次加载(其实还受Tomcat版本和文件系统的影响,不一定是按字母顺序加载)!!!

由ClassLoader的双亲委托模式加载机制我们可以知道,假设两个包名和类名完全相同的class文件不再同一个jar包,如果一个class文件已经被加载java虚拟机里了,那么后面的相同的class文件就不会被加载了。

应用用的是tomcat7,加载lib是按字母顺序从上到下加载jar包,当然async-http-clientnetty-all先加载,自然DefaultChannelId.class来自于async-http-client.jar,可以验证一下:

  • 好了,知道的问题的本质,接下来该知道怎么解决了,个人想了几种方案:

       1)项目中引入http-client耦合的同名netty高版本的class文件,让类加载器优先从这里加载到。当然,如果你的项目还要打包成SDK给其他项目调用的时候,得三思这种方案了,不然又回到了解决http-client嵌入netty的死循环;

       2)修改Tomcat下的context.xml,指定类从哪个jar包加载(未实验,感觉这种方式有点不雅):

<Context>

    <!-- Default set of monitored resources. If one of these changes, the -->
    <!-- web application will be reloaded. -->
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
    <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>

    <!-- Uncomment this to disable session persistence across Tomcat restarts -->
    <!-- <Manager pathname="" /> -->
   <Resources>
      <PreResources className="org.apache.catalina.webresources.FileResourceSet" base="/home/webroot/ls-static-web/WEB-INF/lib/ls-aacustom-common-1.0-SNAPSHOT.jar" webAppMount="/WEB-INF/lib/ls-aacustom-common-1.0-SNAPSHOT.jar" />

     <PreResources className="org.apache.catalina.webresources.FileResourceSet" base="/home/webroot/ls-static-web/WEB-INF/lib/ls-aacustom-static-1.0-SNAPSHOT.jar" webAppMount="/WEB-INF/lib/ls-aacustom-static-1.0-SNAPSHOT.jar" />
</Resources>

</Context>

       3)重构项目用到HttpClient代码,升级到高版本并使用新的类替换;

 
作者:Jorce


参考文章:

[1]. tomcat加载jar包顺序
[2]. NoSuchMethodError报错和Tomcat的jar包加载顺序
[3]. Java中重名类冲突处理机制和Jar包加载顺序
[4]. 重新看待Jar包冲突问题及解决方案

相关文章

微信公众号

最新文章

更多