java反编译jar包_一次排查Java的java.lang.NoSuchFieldError经历

我负责项目使用的别的团队的包,升级了包之后。项目启动后会报出java.lang.NoSuchFieldError的错误,但是这个出错的这个字段存在。

由于是公司项目,不便于展示使用的包,并且没有依赖的这两个包的源码。我自己做了个demo用于复现问题。

依赖结构,项目依赖了test2.jar,test2.jar又依赖了test1.jar5539e9c0f6e49f1b8b5faa8dbad1f542.png

在升级了test1.jar后,项目运行中出现了错误。
错误信息f8fdbc5fa2ece5e03fade7a92bf30af8.png

排查问题

java.lang.NoSuchFieldError错误一般发生在升级包后,新包和使用它的包不兼容。

首先进异常栈显示的问题代码那里(test2.jar中的Test2#test),代码是idea反编译出来的代码

    public static String test() {
return (String)Test1.list.get(0); // 第8行
}

这段代码观察是没什么问题的,Test1.list.get(0)test1.jar中的类。点击Test1也是正常可以进去的,对应的字段list同样存在。分析项目打包出来的依赖,这些类也都有。

Debug这段代码,把断点加在了Test1.list.get(0)这行上面。当运行到这里时,直接通过idea的计算表达式功能,调用这个list,是可以正常执行的。当放开断点,继续执行会同样抛出错误。
因为test1.jar有升级,我怀疑Test1类编译的有问题,用javap命令反编译后,检查,没发现什么问题。(忘记和旧版本包中的类作对比了,这让我花了更多的时间)

我又尝试了直接在项目调用Test1.list.get(0),可以正常执行。但调用Test2.test()就会报错。
现在怀疑Test2类存在问题。我把这个类的代码拷贝到项目里,包路径和名称都保持一致。让程序启动后,使用我项目中的这个类。
重新编译启动,Test2.test()可以正常调用,异常没了。
到这里我估计问题就产生在Test2类了。我对这个包中的类和新编译的类,都反编译成字节码,对比下。javap -c -l Test2.class > Test2-old.txt
旧类的字节码82b2f127061986cb21081a1e76d7a696.png

新类的字节码9fecb07350e33f581d23112e6c95512f.png

对比发现,Test1中的list字段类型变了,原先是ArrayList,新的是List。

原因

a0378baf258c7a5e0719ce53fc945439.png
虽然这段代码不变,但因为Test1.list字段类型发生变化【ArrayList -> List】,编译后的字节码也发生了变化。list字段的类型我们虽然没有显示引用,但编译后的class中已经包含了它的类型信息。一旦的它的类型发生变化,就会在运行过程中抛出错误。
问题产生于test1.jar做了不兼容升级,改了对外暴露字段的类型。导致原有依赖它的包都报错了。

解决办法

由于test1.jar是重要基础包,不让降级,又不能及时找到test2.jat的负责人,我们把test2.jar的问题class重新编译,打包回去。

总结

在这个问题中,底层依赖包升级,导致依赖它的包在使用过程中抛出java.lang.NoSuchFieldError错误。在这件事上,个人觉得这样的错误很低级。底层依赖包做了不兼容升级,还是比较隐蔽的类型改动。
我总结了编程习惯的两个点,可以有效避免这样的不兼容问题。

  1. 通过方法对外提供数据,不要直接暴露方法内部字段。

  2. 方法入参、返回参尽量采用接口。后期改动,换实现类不会导致接口

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值