2009-08-21 51 views
9

我正在開發一個動態加載JAR的應用程序,它包含它使用的一堆類的定義。一切都很好,直到我試圖捕獲動態加載的JAR中的Exception-derived類。在Java中,爲什麼Exception類需要在類加載器需要之前提供給類加載器?

下面的代碼片段顯示問題(DynamicJarLoader是實際加載的JAR類;既有TestClassMyException是在外部JAR):

public static void main(String[] args) { 
    DynamicJarLoader.loadFile("../DynamicTestJar.jar"); 
    try { 
     String foo = new TestClass().testMethod("42"); 
    } catch(MyException e) { } 
} 

當我嘗試運行它,我得到這個:

Exception in thread "main" java.lang.NoClassDefFoundError: dynamictestjar/MyException 
Caused by: java.lang.ClassNotFoundException: dynamictestjar.MyException 
     at java.net.URLClassLoader$1.run(URLClassLoader.java:200) 
     at java.security.AccessController.doPrivileged(Native Method) 
     at java.net.URLClassLoader.findClass(URLClassLoader.java:188) 
     at java.lang.ClassLoader.loadClass(ClassLoader.java:307) 
     at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301) 
     at java.lang.ClassLoader.loadClass(ClassLoader.java:252) 
     at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:320) 
Could not find the main class: dynamicjartestapp.Main. Program will exit. 

如果我更換catch(MyException e)catch(Exception e),程序運行正常。這意味着在JAR已經加載之後,Java 能夠找到TestClass。所以看起來,JVM需要在程序開始運行時定義所有Exception類,而不是在需要時(即達到特定try-catch塊時)定義。

爲什麼會發生這種情況?

編輯

我已經運行一些額外的測試,這確實是很奇怪的。這是MyException完整的源代碼:

package dynamictestjar; 
public class MyException extends RuntimeException { 
    public void test() { 
     System.out.println("abc"); 
    } 
} 

此代碼運行:

public static void main(String[] args) { 
    DynamicJarLoader.loadFile("../DynamicTestJar.jar"); 
    String foo = new TestClass().testMethod("42"); 
    new MyException().test(); //prints "abc" 
} 

這不:

public static void main(String[] args) { 
    DynamicJarLoader.loadFile("../DynamicTestJar.jar"); 
    String foo = new TestClass().testMethod("42"); 
    new MyException().printStackTrace(); // throws NoClassDefFoundError 
    } 

我要指出的是,每當我從NetBeans中運行我的測試,一切都按計劃進行。只有當我從Java的眼中強行刪除外部Jar並從命令行運行測試應用程序時,纔會出現怪異現象。

編輯#2

基礎上的答案,我寫了這個,我想證明我接受了一個確實是正確的:

public static void main(String[] args) { 
    DynamicJarLoader.loadFile("../DynamicTestJar.jar"); 
    String foo = new TestClass().testMethod("42"); 
    class tempClass { 
     public void test() { 
      new MyException().printStackTrace(); 
     } 
    } 
    new tempClass().test(); // prints the stack trace, as expected 
    } 
+0

如果在加載文件語句之前字節代碼重新排序引用了異常,我可以看到發生這種情況,但我對這些問題確定不夠熟悉。 – Yishai 2009-08-21 14:07:24

+0

我在這裏看不到任何證明以這種方式加載JAR的理由。這是一個簡單的例子,但是你的全面代碼的原因是什麼? – duffymo 2009-08-21 14:32:09

+0

@duffymo:這是一個分成兩部分的小程序。較大的一半安裝在本地計算機上,因此用戶不會每次都下載(這相當大);實際的小程序負責加載另一個小程序,然後執行必要的邏輯。通過「加載」,我的意思是動態地改變類路徑,以便它也指向那個Jar。也許有更好的方法來實現這一點? – 2009-08-21 14:45:10

回答

1

您在運行時動態加載JAR DynamicTestJar.jar,但在編譯代碼時將其添加到類路徑中。

因此,當默認類加載器試圖加載main()的字節碼時,它在類路徑上找不到MyException並引發異常。發生這種情況``DynamicJarLoader.loadFile(「../ DynamicTestJar.jar」);`執行!

因此,您必須確保在加載需要它們的類時,您的動態JAR中的類在當前活動的類加載器中可用。之後您可以將JAR添加到類路徑中,特別是不在從其中導入某個類的類中。

6

「所以看來JVM需要的所有在程序開始運行時定義異常類,而不是在需要時定義「 - 不,我不認爲這是正確的。

我敢打賭,你的問題是由於你的一部分,這些潛在的錯誤,其中沒有一個有什麼用JVM:

  1. 你在頂部缺少一個包語句您MyException.java文件。它看起來像你的意思是「包dynamictestjar」,但它不在那裏。
  2. 您確實在您的MyException.java文件中包含正確的package語句,但是當您編譯時,您並未以名爲「dynamictestjar」的文件夾中的MyException.class文件結束。
  3. 將代碼打包到DynamicTestJar中時。jar(星球大戰粉絲,你是)你沒有得到正確的路徑,因爲你沒有壓縮包含文件夾「dynamictestjar」的目錄,所以類加載器看到的路徑是不正確的。

這些都不是由於類加載器的巨大謎團。你需要檢查並確保你正在做你的東西。

爲什麼需要像這樣動態加載JAR?你在做什麼不能簡單地將JAR放在CLASSPATH中並讓類加載器選擇它呢?

我希望這只是一個例子,在Java中並不表示你的「最佳實踐」:

catch(MyException e) { } 

你至少應該打印堆棧跟蹤或使用Log4j來記錄異常。

UPDATE:

我要指出的是,每當我從NetBeans中運行我的測試中,一切都按計劃進行。只有當我從Java的眼中強行刪除外部Jar並從命令行運行測試應用程序時,纔會出現怪異現象。

這表明從命令行運行時,硬連線到JAR路徑的路徑不能滿足要求。

JVM不能以NetBeans的方式工作,而另一種方式沒有它。這完全是關於理解CLASSPATH,類加載器和路徑問題。當你把你排除在外時,它會起作用。

+0

感謝您的回答!有可能我搞砸了一些東西,但請考慮以下兩個事實:i)找到'TestClass',它與'MyException'處於同一個包中。我剛剛檢查過jar文件,'MyException.class'就是它應該是的地方。 ii)當我離開Java可以找到的DynamicTestJar.jar(例如lib /)時,程序運行(只是檢查它)。它也從NetBeans運行。這個問題只會在我刪除jar並把它放在Java看不到的地方時出現(在這種情況下,「..」)。 – 2009-08-21 13:51:21

+0

哦,是的,我沒有吞嚥真正的代碼這樣的例外!這只是說明問題的最簡單的片段,但謝謝你的支持。 – 2009-08-21 13:53:57

+0

「...... Java可以找到它......」 - 沒有關於查看Java內建的/ lib目錄的假設。上帝保佑,你不會將你的代碼添加到JDK目錄中,對嗎?相信我佩德羅,它仍然是你。您的代碼和配置仍然是問題。不要成功找到TestClass,表明你是「正確的」。當你做對了,JVM會發現你的異常,CNF問題就會消失。 – duffymo 2009-08-21 14:13:54

3

MyException是您正在動態加載的JAR中的異常類?

請注意,您是靜態使用類MyException,因爲你在字面上代碼中。

您是在編譯時將DynamicTestJar.jar放入類路徑中,而不是在運行程序時放置?編譯時不要將它放在類路徑中,以便編譯器以靜態方式向您顯示您正在使用JAR的位置。

+0

「靜態」?不確定你的意思。他調用「new」來創建一個實例並調用它的方法。 – duffymo 2009-08-21 14:33:28

+1

@duffymo:當包含'main'方法的類被加載時,它將嘗試加載'MyException'類並失敗,因爲動態加載包含'MyException'的jar的代碼將不會被運行。這就是Jesper通過「靜態使用」的意思。 – 2009-08-21 14:46:16

+0

+1 - 不錯的地方Jesper – 2009-08-21 14:47:08

0

在Java中,所有類都由類加載器和類的FQN唯一標識。

ClassLoaders是分層的,這意味着您的動態加載的類可以訪問其父類的所有類,但父類不能直接訪問其類。

現在,如果你的子類加載器定義了一個擴展父類的類,你的父代碼只能通過反射或父類引用那個類。

如果您認爲所有的Java代碼都是反射式的,這會變得更容易。 IE:

Class.forName("MyException") 

當前類無法訪問子類加載器,因此無法執行此操作。