Java - java.lang.NoSuchMethodError: xxx 错误详解

目录

一.引言

二.问题分析

三.问题解决

1.存在高低版本 Jar 包

2.存在相同版本 jar 包但源码不同

3.maven 依赖存在问题

4.Java 环境问题(解决 👍 )

补充 - 快速定位依赖冲突的来源

四.总结


一.引言

本地执行 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 ~

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BIT_666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值