9

我正在开发一个动态加载 JAR 的应用程序,其中包含它使用的一堆类的定义。一切都很顺利,直到我尝试捕获动态加载的 JAR 中的异常派生类。

以下片段显示了问题(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能够TestClass在 JAR 已经加载之后进行查找。因此,JVM 似乎需要在程序开始运行时定义所有异常类,而不是在需要它们时(即到达特定的 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

4 回答 4

6

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

我敢打赌,您的问题是由于您的这些潜在错误造成的,这些错误都与 JVM 无关:

  1. 您在 MyException.java 文件的顶部缺少一个包语句。看起来您的意思是“package dynamictestjar”,但它不存在。
  2. 您的 MyException.java 文件中确实有正确的包语句,但是当您编译时,您并没有在名为“dynamictestjar”的文件夹中得到 MyException.class 文件。
  3. 当您将代码打包到 DynamicTestJar.jar(星球大战迷,您是)时,您没有获得正确的路径,因为您没有压缩包含文件夹“dynamictestjar”的目录,因此类加载器看到的路径是不正确。

这些都不是由于关于类加载器的巨大谜团。你需要检查并确保你做的事情正确。

为什么需要像这样动态加载 JAR?你在做什么不能通过简单地将 JAR 放在 CLASSPATH 中并让类加载器拾取它来完成?

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

catch(MyException e) { }

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

更新:

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

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

JVM 不能以一种方式与 NetBeans 一起工作,而另一种方式则不能。这完全是为了理解 CLASSPATH、类加载器和路径问题。当你整理好你的,它会工作的。

于 2009-08-21T13:43:22.393 回答
3

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

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

您是否在编译时将 DynamicTestJar.jar 放在类路径中,而不是在运行程序时?编译时不要把它放在类路径中,这样编译器会以静态方式显示你在哪里使用 JAR。

于 2009-08-21T14:06:51.723 回答
1

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

因此,当默认类加载器尝试为 加载字节码时main(),它无法MyException在类路径中找到并引发异常。这发生``DynamicJarLoader.loadFile("../DynamicTestJar.jar");` 执行之前!

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

于 2009-08-21T15:40:44.520 回答
0

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

ClassLoaders 是分层的,这意味着您的动态加载的类可以访问其所有父类的类,但父类不能直接访问其类。

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

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

Class.forName("MyException")

当前类无权访问子类加载器,因此它不能这样做。

于 2009-08-21T15:52:53.947 回答