报错:java.lang.NoSuchMethodError

开课吧开课吧锤锤2021-04-06 14:51

    在学习java和算法过程中,需要使用java的编译和执行命令。但是有时总是会出现各种报错,今天开课吧广场小编整理了开课吧教研老师提供的文章,希望能为遇到此问题的小伙伴帮助发现错误原因,找到对应方法解决问题!

    当应用程序试图调用类(静态或实例)的指定方法,而该类已不再具有该方法的定义时,就会抛出java.lang.NoSuchMethodError错误。简单地说,就是同一个Class有多个版本的实现,并且在运行时调用了缺少方法的那个版本。本文总结了NoSuchMethodError常见原因及其解决方法,如有遗漏或错误,欢迎补充指正。

    运行时抛出NoSuchMethodError的根本原因是什么?

    在实际生产系统中,我们主要关注运行时抛出的NoSuchMethodError错误,该错误轻则导致程序异常终止,严重时甚至会产生不可预知的程序结果,比如支付服务执行异常,实际支付已完成,却向用户返回支付失败。

    运行时抛出NoSuchMethodError错误的根本原因就是:应用程序直接或间接依赖了同一个类的多个版本,并且在运行时执行了缺少方法的版本。如下图所示:

Java

    因此,核心问题就转化为:同一类为什么会有多个版本?哪个版本的类最终会被执行?

    为什么同一个Class会出现多个版本?

    导致JavaClass出现多版本的原因,可以归纳为以下几类:

    JDK版本不一致。常见于编译打包环境使用高版本JDK开发与打包,而实际运行环境的JDK版本较低。例如,本地项目环境JDK版本为1.7,调用Character.isAlphabetic()方法判断当前字符是否为字母;而线上环境JDK版本为1.6,在运行期间就会抛出NoSuchMethodError错误。

    SNAPSHOT版本不一致。常见于本地更新SNAPSHOT版本后,没有执行mvncleandeploy部署,导致线上环境运行时仍然引用了旧版本的SNAPSHOT包。

    Maven依赖生命周期为provided。常见于本地依赖的某组件生命周期为provided,所声明版本仅用于本地编译打包,而线上运行时会通过其他依赖关系加载Jar包。

    同一个Jar包出现了多个版本。常见于Maven依赖未显式指定版本号,导致间接依赖版本冲突,很容易引入低版本的Jar包。

    同一个Class出现在不同的Jar包中。该问题常见于代码拷贝场景,比如基于开源版本定制了一些功能,使用了新的Maven坐标打包发布,此时Maven仲裁机制失效(非常隐蔽,难以排查)。由于JVM类加载器对于同一个类只会加载一次,最终加载的类实现受到Jar包依赖的路径、类声明的先后顺序或文件加载顺序等因素的影响,很可能出现不同机器加载的类实现不一致。

    哪个版本的Class最终会被执行?

    影响Class最终是否被执行的关键因素有两个:Maven依赖仲裁机制和JVM类加载机制,如下图所示:

Java

    首先,Maven依赖仲裁机制决定了打包的优先级,仲裁优先级“从高到低”如下所述:

    优先按照依赖管理[dependencyManagement]元素中指定的版本进行仲裁;

    若无版本声明,则按照“短路径优先”原则(Maven2.0)进行仲裁,即选择依赖树中路径最短的版本;

    若路径长度一致,则按照“第一声明优先”原则进行仲裁,即选择POM中最先声明的版本。

    合理使用Maven依赖仲裁机制可以便捷的管理Jar包版本,而不合理的使用将导致多版本Jar冲突。

    其次,JVM类加载机制决定了Class被加载到JVM的优先级,如果同一个类出现在多个Jar包中,那么在双亲委派类加载机制下,加载该Jar包的类加载器层级越高,该Jar包越先被加载,它所包含的Class越先被执行,如上图所示:

    启动类加载器(BootstrapClassLoader)优先级最高,主要加载JVM运行时核心类,如java.util、java.io等,这些类主要位于$JAVA_HOME/lib/rt.jar文件中。

    扩展类加载器(ExtentionClassLoader)优先级次之,主要加载JVM扩展类,如swing组件、xml解析器等,这些类主要位于$JAVA_HOME/lib/ext/目录下的Jar包中。

    应用类加载器(ApplicationClassLoader),又称系统类加载器,优先级再次之,它会加载Classpath环境变量里定义的路径中的Jar包和目录,通常我们自己编写的代码或依赖的第三方Jar包都是由它来加载。

    除了上述两种原因外,在同一个ClassLoader下,如果存在一个Class出现在不同的Jar包中,那么文件系统的文件加载顺序也可能会影响最终的加载结果。因此,应该尽量保证开发/测试/生产系统环境一致性。

    如何解决NoSuchMethodError错误?

    虽然抛出NoSuchMethodError错误的原因多种多样,但本质上是由于编译时类路径与运行时类路径不一致。因此,通用的定位思路可以归纳为以下3步:

    1、定位异常Class的全限定类名与调用方,通常可以在应用日志抛出的异常堆栈中获取。如下图所示:

Exception in thread "main" java.lang.NoSuchMethodError: com.xxx.AsyncAppender.append(Ljava/lang/String;)Ljava/lang/String;	
  at com.xxx.ProvokeNoSuchMethodError.main(ProvokeNoSuchMethodError:7)	
  at ……

    2、定位异常Class的来源,可以通过Arthas等在线诊断工具反编译,如jadcom.xxx.AsyncAppender,获取该类运行时的源码、ClassLoader、Jar包位置等信息。

Java

    1、根据ClassLoader和Jar包全路径名等信息,判断是类加载、Maven仲裁或其他原因,并对应的加以解决。例如加载了同一个Jar包的低版本实现,则在MavendependencyManagement中指定版本,或者移除间接依赖中的低版本(提示:执行mvndependency:tree命令,可以输出Maven依赖拓扑关系)。

    其他Jar包冲突问题

    本文介绍的Jar包冲突解决方法,除了解决java.lang.NoSuchMethodError以外,对其他相似问题也具备一定的参考价值。

    例如java.lang.ClassNotFoundException,即加载不到指定类,通常是Maven仲裁选错了版本,如本地开发阶段调用了1.2.0版本,而打包时采用了1.0.0版本的Jar包。同理,java.lang.NoClassDefFoundError和java.lang.LinkageError也可以基于上述思路进行排查。

    此外,如果类和方法名都保持不变,但是内部实现有变化,在多版本冲突场景下,不会抛出异常,但程序行为跟预期不一致,此时,也可以基于上述思路进行排查诊断。

    以上开课吧小编今日整理的“java.lang.NoSuchMethodError”Java报错相关一文了,希望为遇到相关报错问题的朋友提供参考,更多Java报错内容尽在开课吧广场Java问答频道!

有用
分享
全部评论快来秀出你的观点
登录 后可发表观点…
发表
暂无评论,快来抢沙发!
高并发编程训练营