NoSuchMethodError通用解決方案

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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章