Java - java.lang.NoSuchMethodError: xxx 错误详解
java.lang.NoSuchMethodError 报错详细分析与解决方案。
目录
一.引言
本地执行 java -classpath $jar $class 时报错:
java.lang.NoSuchMethodError:
com.alibaba.fastjson.JSONObject.getOrDefault
(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object
可是 $jar 内 maven 配置已打入 fastJson 依赖且未 Provided:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency
于是开始踩坑之旅。
二.问题分析
val json = JSON.parseObject(info)
val array = JSON.parseArray(json.getOrDefault("data", "[]").toString).toArray().map(_.toString)
我的代码大致为 Json 解析,先解析 Object 再解析 array,由于报错位置提示为 noSuchMethod: getOrDefault,所以说明 json.parseObject 这行执行没有问题,即 fastJson 依赖正常打入,所以可以判断不是因为 maven 未打入依赖造成。所以可能是如下问题:
jar 包冲突: 多个项目都存在 fastJson 依赖,代码读取了低版本的 fastJson,低版本无该方法导致调用失败。这里其实涉及一个常识问题,如果报错 NoClassDefFound 报错,那么大概率是打包失败或者未提供相关 jar 包,而 noSucnMethod 报错除了方法名写错的低级失误外,大部分是 jar 包冲突导致。
三.问题解决
1.存在高低版本 Jar 包
A.jar-fastJson 低版本,B.jar-fastJson 高版本,本地执行程序读取低版本 fastJson 有可能导致上述问题,所以需要排查的是 jar 包中哪些项目包含了 fastJson:
这里可以使用 java -verbose:class,该方法会在控制台打印程序运行加载的类的情况,使用该命令即可查看程序内哪些依赖包含 fastJson,然后再去对应项目查找是否高低版本。
java -verbose:class -classpath your.jar your.class
执行后 cat log | grep fastJson 即可看到 fastJson 的文件来自于哪个 jar 包,后方红色标识即为来源:
经过检查,所有 fastJson 类文件均加载于同一 jar 包,即我本身的项目 jar 包依赖,所以排除高低版本问题。
Tips:
这里查看项目依赖采用了 -verbose 方法,该方法除了打印 jvm 运行时加载依赖,也可以传入 gc 或者 jni 查看虚拟机响应,其次也可以借助 maven :
mvn -Dverbose dependency:tree
该方法可以按层次打印出jar包相关依赖于版本,可以更好地方便定位 jar 包方法冲突与版本冲突,有需要可以参考我之前的文章: java.lang.NoSuchMethodError 之 依赖冲突解决方案,之前写作本文主要是线上任务出现该报错,本文主要是在本地执行 java 任务报错,所以虽然场景不同,但是报错相同,两篇文章的方法都可以借鉴到排查该方法背后的 jar 包冲突。
2.存在相同版本 jar 包但源码不同
这种情况比较少,一般多见于引用非官方依赖,两个jar包中包含同一依赖,两个依赖的版本一致,但是源码不一致,这种情况不好使用上述依赖树方法排查到,这里说下解决方案:
A.优先排除
最直接的方法是排除掉冲突 jar 包中的一个,只保留另一个 jar 包
B.调整顺序
如果上述方法行不通可以尝试修改 jar 包读取顺序,pom.xml 是按照 dependency 的顺序从上往下读,所以可以把要优先用到的依赖对应的 jar 包放在靠前位置加载
Tips:
如果使用 --jars 传包,同样要保证要用的 jar 包顺序在前
3.maven 依赖存在问题
既然不是高低版本问题,那就定位到唯一 jar 包,我怀疑是 maven 打包异常,所以决定 provided 该依赖,随后从外部传入。
首先注释掉 jar 包中的 fastJson 依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
<scope>provided</scope>
</dependency>
随后本地执行传入 fastJson 依赖,本地传入依赖可以使用 -Djava.ext.dirs 参数,该方法指定一个路径文件夹,文件夹下包含所以项目需要的额外依赖,有点类似项目中的 lib 文件夹:
java -verbose:class -Djava.ext.dirs=$dir -classpath your.jar your.class
这里在本地创建一个文件夹放入 fastJson 依赖即可:
再次执行任务,依旧出现同样问题,因此唯一 jar 包的问题也不存在了。
4.Java 环境问题(解决 👍 )
方法1高低版本排除了多 jar 包的情况,方法二单独 provided 排除了单 jar 包的情况,所以剩下的只剩环境问题了,回想起刚才 -verbose 打印的日志,java 版本都是 1.7.0:
查看本地 Java 环境:
echo $JAVA_HOME
/usr/local/jdk1.8.0_131
原来是 java 版本不匹配,修改提交脚本再次尝试,问题解决:
$JAVA_HOME/bin/java -classpath your.jar your.class
补充 - 快速定位依赖冲突的来源
依赖冲突常见于 Hadoop 类、Google 类、Common 类等依赖,由于可能多个依赖包存在上述类,除了通过 mvn-tree 和 idea 快捷键遍历搜索外,java 可以直接使用 getResource 方法获取依赖冲突的位置对应 class 调用了哪个 jar 包:
println(this.getClass.getResource("/com/google/common/collect/Sets.class"))
例如现在有多行代码,报错在第 N 行且异常栈指到 com.google.common.collect.Sets,我们想要获取调用的 Sets 类来自哪里,就可以在第 N 行上面加入上述语句 👆 即可打印对应依赖引用位置,非常的方便:
...
...
第N行
...
...
四.总结
这个问题困扰了一天,期间多半时间花在 maven 打包问题排查与 fastJson 依赖冲突的问题上,但是结果是最基本的 java 环境不匹配问题。今日踩坑,明日避坑,虽然期间尝试了多种方法最终解决方法如此简单,但是找到简单方法过程中,熟悉了不同的 jar 包冲突分析方法,后续再有类似问题也可以更快的定位和解决,所以还是收获满满,可能这就是学习的魅力吧,最后希望大家都可以少点 bug ~
更多推荐
所有评论(0)