我负责项目使用的别的团队的包,升级了包之后。项目启动后会报出
java.lang.NoSuchFieldError
的错误,但是这个出错的这个字段存在。
由于是公司项目,不便于展示使用的包,并且没有依赖的这两个包的源码。我自己做了个demo用于复现问题。
依赖结构,项目依赖了test2.jar
,test2.jar
又依赖了test1.jar
。
在升级了test1.jar
后,项目运行中出现了错误。 错误信息
排查问题
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
旧类的字节码
新类的字节码
对比发现,Test1
中的list
字段类型变了,原先是ArrayList,新的是List。
原因
虽然这段代码不变,但因为Test1.list
字段类型发生变化【ArrayList -> List】,编译后的字节码也发生了变化。list
字段的类型我们虽然没有显示引用,但编译后的class中已经包含了它的类型信息。一旦的它的类型发生变化,就会在运行过程中抛出错误。 问题产生于test1.jar
做了不兼容升级,改了对外暴露字段的类型。导致原有依赖它的包都报错了。
解决办法
由于test1.jar
是重要基础包,不让降级,又不能及时找到test2.jat
的负责人,我们把test2.jar
的问题class重新编译,打包回去。
总结
在这个问题中,底层依赖包升级,导致依赖它的包在使用过程中抛出java.lang.NoSuchFieldError
错误。在这件事上,个人觉得这样的错误很低级。底层依赖包做了不兼容升级,还是比较隐蔽的类型改动。 我总结了编程习惯的两个点,可以有效避免这样的不兼容问题。
- 通过方法对外提供数据,不要直接暴露方法内部字段。
- 方法入参、返回参尽量采用接口。后期改动,换实现类不会导致接口