2009-08-21 19 views
9

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 
    } 
+0

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

+0

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

+0

@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? –

Respuesta

1

Está cargando el JAR DynamicTestJar.jar dinámicamente en el tiempo de ejecución pero lo agrega al classpath cuando compila el código.

Así que cuando el cargador de clases predeterminado intenta cargar el bytecode para main(), no puede encontrar MyException en el classpath y arroja una excepción. Esto sucede antes de `` DynamicJarLoader.loadFile ("../ DynamicTestJar.jar"); `se ejecuta!

Por lo tanto, debe asegurarse de que las clases de su JAR dinámico estén disponibles en el cargador de clases actualmente activo cuando se cargue una clase que las necesite. Puede agregar el JAR al classpath después, especialmente no en una clase que importe algo de él.

6

"Así que parece que la JVM necesita todo Las clases de excepción se definirán cuando el programa comience a ejecutarse, y no cuando se necesiten "- no, no creo que esto sea correcto.

Estoy apostando que su problema se debe a estos posibles errores de su parte, ninguno de los cuales tiene nada que ver con la JVM:

  1. Te estás perdiendo una sentencia de paquete en la parte superior de su Archivo MyException.java Parece que quería decir que era un "paquete dinámicotestjar", pero no está allí.
  2. Usted tenía la declaración de paquete correcta en su archivo MyException.java, pero cuando compiló no terminó con el archivo MyException.class en una carpeta llamada "dynamictestjar".
  3. Cuando empaquetó su código en DynamicTestJar.jar (fan de Star Wars, lo eres) no obtuviste el camino correcto porque no has ZIP el directorio que contiene la carpeta "dynamictestjar", por lo que la ruta que ve el cargador de clases es incorrecta.

Ninguno de estos se debe a grandes misterios sobre el cargador de clase. Debes verificar y asegurarte de que estás haciendo tus cosas correctamente.

¿Por qué necesita cargar el JAR dinámicamente de esta manera? ¿Qué estás haciendo que no podría lograrse simplemente colocando el JAR en el CLASSPATH y dejando que el cargador de clases lo recoja?

espero que esto es solo un ejemplo y no es indicativo de sus "mejores prácticas" en Java:

catch(MyException e) { } 

Al menos debe imprimir el seguimiento de la pila o utilizar Log4J para registrar la excepción.

ACTUALIZACIÓN:

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.

Esto sugiere que la ruta que ha cableado en la ruta al JAR no se cumple cuando se ejecuta desde la línea de comandos.

JVM no funciona de una manera con NetBeans y de otra manera sin ella. Se trata de entender CLASSPATH, el cargador de clases y los problemas de ruta. Cuando clasifiques a los tuyos, funcionará.

+0

¡Gracias por responder! Es posible que haya estropeado algo, pero considere estos dos hechos: i) 'TestClass' se encuentra, y está en el mismo paquete que' MyException'. Acabo de verificar el archivo jar y 'MyException.class' está donde debería estar. ii) Cuando dejo 'DynamicTestJar.jar' donde Java puede encontrarlo (por ejemplo, lib /), el programa se ejecuta (simplemente lo comprobó). También se ejecuta desde NetBeans. El problema solo se manifiesta cuando elimino el jar y lo coloco en algún lugar donde Java no se vea (en este caso, ".."). –

+0

Oh, sí, no estoy tragando Excepciones como esta en código real! Este es solo el fragmento más simple posible que ilustra el problema, pero gracias por el aviso. –

+0

"... donde Java puede encontrarlo ..." - no hay ninguna suposición acerca de buscar en un directorio/lib integrado en Java. Dios no lo quiera, no está agregando su código a los directorios de JDK, ¿verdad? Créeme Pedro, todavía eres tú. Su código y configuración sigue siendo el problema. No tenga éxito en encontrar TestClass como una indicación de que usted es "correcto". Cuando lo haces bien, la JVM encontrará tu excepción y ese problema de CNF desaparecerá. – duffymo

3

¿MiExcepción es una clase de excepción en el JAR que está cargando dinámicamente?

Tenga en cuenta que usted es estáticamente utilizando la clase MyException, como lo tiene literalmente en su código.

¿Está colocando DynamicTestJar.jar en su classpath mientras compila, pero no mientras ejecuta el programa? No lo coloque en el classpath mientras compila, para que el compilador le muestre dónde está utilizando el JAR de una manera estática.

+0

"estáticamente"? No estoy seguro de lo que quieres decir con eso. Está llamando "nuevo" para crear una instancia e invocar métodos sobre ella. – duffymo

+1

@duffymo: cuando se carga la clase que contiene el método 'main' intentará cargar la clase' MyException' y fallará porque el código que carga dinámicamente el contenedor que contiene 'MyException' no se habrá ejecutado todavía. Eso es lo que Jesper quiere decir con "usar estáticamente". –

+0

+1 - buen lugar Jesper –

0

En Java, todas las clases están identificadas de forma única por el cargador de clases y el FQN de la clase.

Los ClassLoaders son jerárquicos, lo que significa que la clase cargada dinámicamente tiene acceso a todas las clases de sus padres, pero el padre no tiene acceso directo a sus clases.

Ahora, si su cargador de clases hijo define una clase que amplía su elemento primario, el código de su padre puede referirse a esa clase solo mediante reflexión o por la clase padre.

Si piensas en todos los códigos Java como reflexivos, esto se vuelve más fácil. IE:

Class.forName("MyException") 

La clase actual no tiene acceso al cargador de clases hijo, por lo que no puede hacer esto.

Cuestiones relacionadas