Estoy desarrollando una aplicación que carga dinámicamente un JAR, que contiene la definición de un grupo de clases que utiliza. Todo fue bien hasta que intenté atrapar una clase derivada de Exception que está en el JAR cargado dinámicamente.En Java, ¿por qué las clases de excepción deben estar disponibles para el cargador de clases antes de que sean necesarias?
El siguiente fragmento de código muestra el problema (DynamicJarLoader
es la clase que en realidad carga el JAR; tanto TestClass
y MyException
hay en el frasco externo):
public static void main(String[] args) {
DynamicJarLoader.loadFile("../DynamicTestJar.jar");
try {
String foo = new TestClass().testMethod("42");
} catch(MyException e) { }
}
Cuando intento ejecutarlo, me sale esto:
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.
Si sustituyo catch(MyException e)
con catch(Exception e)
, el programa funciona muy bien. Esto significa que Java es capaz de encontrar TestClass
después de que el JAR ya se haya cargado. Por lo tanto, parece que la JVM necesita que se definan todas las clases de Excepción cuando el programa comienza a ejecutarse, y no cuando se necesitan (es decir, cuando se alcanza ese bloque de prueba en particular).
¿Por qué sucede esto?
EDITAR
me he encontrado algunas pruebas adicionales, y esto es de hecho bastante extraño. Esta es la fuente llena de MyException
:
package dynamictestjar;
public class MyException extends RuntimeException {
public void test() {
System.out.println("abc");
}
}
se ejecuta este código:
public static void main(String[] args) {
DynamicJarLoader.loadFile("../DynamicTestJar.jar");
String foo = new TestClass().testMethod("42");
new MyException().test(); //prints "abc"
}
Esto no es así:
public static void main(String[] args) {
DynamicJarLoader.loadFile("../DynamicTestJar.jar");
String foo = new TestClass().testMethod("42");
new MyException().printStackTrace(); // throws NoClassDefFoundError
}
Debo señalar que cada vez que corro mis pruebas de NetBeans , todo va de acuerdo al plan. La rareza comienza solo cuando remuevo con fuerza el Jar externo de los ojos de Java y ejecuto la aplicación de prueba desde la línea de comando.
editar # 2
Sobre la base de las respuestas, he escrito esto, que creo que demuestra la acepté es de hecho la derecha:
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
}
Pude ver que esto sucediera si el reordenamiento del código de bytes hace referencia a la Excepción antes de la declaración del archivo de carga, pero no estoy lo suficientemente familiarizado con esos problemas para estar seguro. – Yishai
No veo nada aquí que justifique cargar el JAR de esta manera. Es un ejemplo simple, pero ¿cuál es el motivo en su código completo? – duffymo
@duffymo: Es un applet que se ha dividido en dos. La mitad más grande está instalada en la computadora local para que el usuario no la descargue cada vez (es bastante grande); el applet real es responsable de cargar el otro, y luego hacer la lógica necesaria. Al "cargar", en realidad me refiero a cambiar dinámicamente la ruta de clase para que también apunte a Jar. Tal vez hay una mejor manera de lograr esto? –