我正在开发一个动态加载JAR的应用程序,它包含它使用的一堆类的定义。一切都很顺利,直到我试图捕获动态加载的JAR中的Exception派生类。
以下代码段显示了问题(DynamicJarLoader
是实际加载JAR的类; TestClass
和MyException
都位于外部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
}
答案 0 :(得分:6)
“因此看起来JVM需要在程序开始运行时定义所有Exception类,而不是在需要它们时” - 不,我不认为这是正确的。
我认为你的问题是由于你的这些潜在错误造成的,其中没有一个与JVM有任何关系:
这些都不是由于关于类加载器的大谜。你需要检查并确保你正确地做你的东西。
为什么需要像这样动态加载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")
当前类无法访问子类加载器,因此无法执行此操作。