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

CSDN博客 · · 1843 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

 

背景:

  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相关包的:

111

222

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包冲突问题及解决方案

本文来自:CSDN博客

感谢作者:CSDN博客

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

1843 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传