今天项目线上测试出现了一个问题,一个War包启动失败(使用Jetty Runner启动),报的错是: Caused by:
java.lang.NoSuchMethodError: org.apache.cxf.transport.AbstractTransportFactory.<init>(Ljava/util/List;Lorg/apache/cxf/Bus;)V
这个我一看第一反应是肯定是某个依赖的jar包的版本弄错了。我看了一下AbstractTransportFactory这个类,是属于cxf-api里面的(我们用的版本是2.6.2), 于是我就去Jetty解压出来的war包的目录下的lib里面看,发现里面有,cxf-api(2.6.2), cxf-core(3.1.12)等几个jar包,那就奇怪了,cxf-api的版本是对的,我甚至把它解压出来反编译了AbstractTransportFactory类来看,也是没有问题的。百思不得其解。后来同事Jun Gong提醒我说既然它报了那个错,就必定是加载到了错误的class,你这样看是看不出来的,你要把JVM加载的所有的class都打印出来,看最终加载到哪个包里面的类。
Google了一下,我在程序的java启动脚本里面加上了 -verbose:class
这个参数: java -verbose:class ...... 1 > /tmp/test.log &
, 1 > /tmp/test.log
是把输出重定位到一个临时文件里面,方便查询。当我查看的时候,居然看到AbstractTransportFactory这个类是从cxf-core(3.1.12)这个包里面加载进来的,就是说cxf-api(2.6.2)和cxf-core(3.1.12)这两个包里面都有FQN(Full Qualified Name)为 org.apache.cxf.transport.AbstractTransportFactory
的类!!而JVM加载的时候,根据FQN去加载,如果加载到cxf-core里面的,就不会再加载cxf-api里面的,而我们需要的是cxf-api里面的!关键问题是,我们根本没法控制JVM的加载顺序!这就是为什么这个问题之前没有发生,属于概率问题。
虽然最后找到了Root Cause,主要还是我们项目里面各种依赖没有统一和处理好各种依赖关系。但我想吐槽一下,活该cxf这项目半死不活,居然在两个包里面用同一个FQN,这实在是bad practice的典范了。
原文 http://ericcenblog.com/2018/07/31/ji-yi-ci-lei-wu-ran-wen-ti-ding-wei/