從NoSuchMethodError看jvm編譯和class加載方式

今天在寫自己的項目的時候出現了一個Exception in thread "main" java.lang.NoSuchMethodError的異常,但是我的代碼在編譯過程中是沒有任何問題的。
先來講一下我的項目關鍵的結構


我的項目引用了兩個jar包,暫定爲jarA(version:1.0)和jarB(version:2.0),但是jarA中又引用了jarB(version:1.0),這個時候我調用了jarA中的一個方法,假定爲invokeMethodA(),該方法會調用jarB中的一個方法,這個方法jarA中的jarB(version:1.0)是這樣的格式invokeMethodB(String s,Throwable t);但是這個方法在jarB是這樣的invokeMethodB(String s,Object... o);相當於jarB(version:2.0)進行了一個升級。當我調用這個方法的時候jarA的invokeMethodA()方法,則會報出這樣的問題Exception in thread "main" java.lang.NoSuchMethodError: com.cn.b.BClass.invokeMethodB(Ljava/lang/String;Ljava/lang/Throwable;)V。這是因爲在jarA中的jarB(version:1.0)中,invokeMethodB在jarB(version:1.0)中編譯的時候是編譯成invokeMethodB(Ljava/lang/String;Ljava/lang/Throwable;)V的形式,只有完全匹配上才能夠調得通。但是在這邊的項目中有完全同包名同類名同方法但是參數格式不一樣的方法,被編譯成(Ljava/lang/String;[Ljava/lang/Object;)V的格式,而BClass是優先加載jarB(version:2.0)中的BClass。


這裏得出兩個結論
1、對於同包名同類名的類,類加載器會加載引用鏈最短的jar包內的類
2、編譯器在方法調用前就完全確定一個方法的一一匹配



下面是我簡單的自己寫了方法進行測試

先看看項目結構



先看測試方法的Test類

public class Test {
    public static void main(String[] args) {
        new PrintUtil().print("dd");
    }
}


再看看jarB(version:2.0)模擬的T1實現

public class T1 {
    public void doit(String dd, Object... o) {
        System.out.println(dd + " --- object...");
    }
}


下面看jar-test模塊所引用的jar包 jar1(對應上面說的jatA)

先來看PrintUtil類(類似於項目中要調用第三方jar包的方法)

public class PrintUtil {
    public void print(String str) {
        T1 t1 = new T1();
        t1.doit(str,new Exception());
    }
}<

最後看jarA中對應jarB(version:1.0)對應的類方法實現T1.print()

public class T1 {

    public void doit(String dd, Throwable a) {
        System.out.println(dd +" --- jar1");
    }
}


現在把整個項目編譯一下,沒有任何問題。

當運行test的main方法時,會發現,異常了!

Exception in thread "main" java.lang.NoSuchMethodError: com.java.jar1.T1.doit(Ljava/lang/String;Ljava/lang/Throwable;)V
	at com.java.jar1.PrintUtil.print(PrintUtil.java:13)
	at com.java.jar.test.Test.main(Test.java:13)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:606)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)

這個其實就是jarA中編譯了自己的T1類,但是項目main方法運行的是在jar-test模塊,jar-test模塊優先加載自己的T1類,這個時候他們編譯下來,都不會報錯,因爲jarA中找到doit()的方法,而且能夠匹配上。但是最終運行的時候,程序的調用順序是main() ->print()->T1.doit(Ljava/lang/String;Ljava/lang/Throwable;)V,那麼這個時候按理說jar-test的T1.doit(Ljava/lang/String;[Ljava/lang/Object;)V也可以匹配上這樣的調用。這就是問題的關鍵,編譯後的class文件必須一一匹配上參數纔行,就算是子類和父類的關係也不會匹配上,這裏大家可以試一試(我試過了String 和Object的子父類關係,也是不行)。


歡迎大家有問題與我一同討論。


測試項目下載地址:http://download.csdn.net/download/codingtu/9937638   (解壓密碼:codingtu)

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