NoSuchMethodError 这种错误可能由两个原因导致。
1、项目运行时加载了错误jar包下的class
2、同一个类型的jar包的不同版本同时存在classpath中。
一、加载了错误jar包下的class
参考下面这个例子:
java.lang.NoSuchMethodError: org.apache.xmlbeans.XmlOptions.setEntityExpansionLimit(I)Lorg/apache/xmlbeans/XmlOptions;
at org.apache.poi.ooxml.POIXMLTypeLoader.<clinit>(POIXMLTypeLoader.java:43)
at org.apache.poi.xssf.model.ThemesTable.<init>(ThemesTable.java:86)
错误发生的背景:
POI 3.10 升级到 4.0.1,而POI会使用到xmlbeans.jar这个包。前者(v3.10)使用xmlbeans-2.3.0.jar,后者(v4.0.1)使用xmlbeans-3.0.2.jar。升级时,也确定xmlbeans 3.0.2.jar在classpath中,如下:
[INFO] | | \- org.apache.xmlbeans:xmlbeans:jar:3.0.2:compile
查看XmlOptions.class的源码,也确实有setEntityExpansionLimit()方法。
分析思路:
觉得很不可思议,所以在项目代码中加入如下代码,来查看XmlOption.class是从哪个jar包中加载的,示例代码如下(作为参考)
ClassLoader classloader = XmlOptions.class.getClassLoader();
URL res = classloader.getResource("org/apache/xmlbeans/XmlOptions.class");
String path = res.getPath();
LOGGER.debug("XmlOptions.class loaded from " + path)
运行项目 ,相关log如下:
XmlOptions.class loaded from file:/D:/xxx/Weblogic12/weblogic_home/wlserver/modules/com.bea.core.xml.xmlbeans.jar!/org/apache/xmlbeans/XmlOptions.class
发现XmlOption.class是从weblogic的module目录下的com.bea.core.xml.xmlbeans.jar中加载的,使用java decompiler 工具查看该jar包下的XmlOption.class,确实没有setEntityExpansionLimit()这个方法。
那么,为什么项目发布到服务器后,会从weblogic中加载,而不是直接从项目classpath中加载呢?
默认情况下,所有的 class都会优先从服务器和jdk中加载,其次是项目classpath。如果需要修改class的加载优先级,让某些class优先从项目classpath中加载,需要在weblogic.xml的 <prefer-application-packages>标签中进行相关配置(不再赘述)。
解决方案:
在weblogic.xml的 <prefer-application-packages>标签中指定相关jar的加载优先级。
<package-name>org.apache.xmlbeans.*</package-name>
二、同一个类型的jar包的不同版本同时存在classpath中。
参考如下例子:
java.lang.NoSuchMethodError: org.apache.commons.codec.binary.Base64.encodeBase64String([B)Ljava/lang/String;
分析:
Base64.class所在的jar包commons-codec-1.5.jar确定在classpath中,而且该jar下面的Base64.class也有相应的方法。
使用相同的方法:在项目代码中引入如下代码:
ClassLoader classloader = Base64.class.getClassLoader();
URL res = classloader.getResource("org/apache/commons/codec/binary/Base64.class");
String path = res.getPath();
LOGGER.debug("Base64.class loaded from " + path)
重新编译项目,发布运行,查看log:
Base64.class loaded from file:/D:/xxx/Weblogic12/weblogic_home/user_projects/domains/base_domain/servers/AdminServer/tmp/_WL_user/projectname/89k7t8/war/WEB-INF/lib/commons-codec-1.2.jar
Base64.class从commons-codec-1.2.jar加载的,不是从commons-codec-1.5.jar加载的。
而log中所指向的目录正好是项目发到服务器时,项目的所有jar指向的目录。该目录下同时存在commons-codec-1.2.jar和commons-codec-1.5.jar(commons-codec的不同版本)。
使用java decomplier打开commons-codec-1.2.jar,发现这个jar下的Base64.class没有encodeBase64String()方法。
解决方案:
打开项目的pom.xml文件,选择Dependency Hierarchy(依赖层次),找到commons-codec-1.2.jar,发现该jar是被某个框架默认引入的,而不是在pom.xml通过<dependency>标签明文引入的。所以,将commons-codec-1.2.jar从相关框架中exclude。
总结
NoSuchMethodError这种错误必定是相关的class加载不对,定位到加载的哪个jar下面的class是特别重要的。定位代码如下(仅作参考)
ClassLoader classloader = ClassA.class.getClassLoader();
URL res = classloader.getResource("path of ClassA");
// eg:org/apache/xmlbeans/XmlOptions.class
String path = res.getPath();
LOGGER.debug("ClassA.class loaded from " + path)