2009-08-21 72 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") 

当前类无法访问子类加载器,因此无法执行此操作。