在Java中,为什么在必要之前需要为类加载器提供Exception类?

时间:2009-08-21 13:35:53

标签: java exception classloader noclassdeffounderror

我正在开发一个动态加载JAR的应用程序,它包含它使用的一堆类的定义。一切都很顺利,直到我试图捕获动态加载的JAR中的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),程序运行正常。这意味着Java 能够在加载JAR之后找到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
  }

4 个答案:

答案 0 :(得分:6)

“因此看起来JVM需要在程序开始运行时定义所有Exception类,而不是在需要它们时” - 不,我不认为这是正确的。

我认为你的问题是由于你的这些潜在错误造成的,其中没有一个与JVM有任何关系:

  1. 您错过了MyException.java文件顶部的包语句。看起来你认为它是“package dynamictestjar”,但它并不存在。
  2. 您的MyException.java文件中确实有正确的包语句,但是在编译时,您没有在名为“dynamictestjar”的文件夹中找到MyException.class文件。
  3. 当您将代码打包到DynamicTestJar.jar(星球大战粉丝,你是)时,你没有得到正确的路径,因为你没有压缩包含文件夹“dynamictestjar”的目录,所以这个类的路径装载机看到的是不正确的。
  4. 这些都不是由于关于类加载器的大谜。你需要检查并确保你正确地做你的东西。

    为什么需要像这样动态加载JAR?你只是把JAR放在CLASSPATH中并让类加载器把它拿起来,这是无法实现的?

    我希望这只是一个例子,并不代表你在Java中的“最佳实践”:

    catch(MyException e) { }
    

    您至少应该打印堆栈跟踪或使用Log4J来记录异常。

    更新:

      

    我应该指出,每当我从NetBeans运行测试时,一切都按计划进行。只有当我强行从Java眼中移除外部Jar并从命令行运行测试应用程序时,才会开始出现这种奇怪现象。

    这表明当您从命令行运行时,您已经硬连接到JAR路径的路径不满意。

    JVM在NetBeans中不是单向工作,没有它的另一种方式。这一切都与理解CLASSPATH,类加载器和路径问题有关。当你对它进行排序时,它会起作用。

答案 1 :(得分:3)

MyException是您正在动态加载的JAR中的异常类吗?

请注意,您使用类MyException是静态,因为您在代码中确实存在它。

在编译时是否将DynamicTestJar.jar放在类路径中,但是在运行程序时却没有?编译时不要将它放在类路径中,以便编译器以静态方式显示您在哪里使用JAR。

答案 2 :(得分:1)

您在运行时动态加载JAR DynamicTestJar.jar,但在编译代码时将其添加到类路径中。

因此,当默认的类加载器尝试加载main()的字节码时,它无法在类路径上找到MyException并抛出异常。这种情况发生在之前``DynamicJarLoader.loadFile(“../ DynamicTestJar.jar”);`已执行!

因此,当加载需要它们的类时,您必须确保动态JAR中的类在当前活动的类加载器中 >。之后您可以将JAR添加到类路径中,尤其是在从其中导入内容的类中。不过。

答案 3 :(得分:0)

在Java中,所有类都由类的类加载器和FQN唯一标识。

ClassLoader是分层的,这意味着您的动态加载类可以访问其所有父类,但父级没有对其类的任何直接访问权。

现在,如果您的子类加载器定义了一个扩展父类的类,那么父类的代码只能通过反射或父类引用该类。

如果您认为所有Java代码都是反射性的,那么这就变得更容易了。 IE:

Class.forName("MyException")

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