2010-10-19 10 views
17

Antecedentes:
Tengo una clase de prueba principal que tiene muchos botones, cada uno de los cuales ejemplifica una clase que estoy codificando/probando. Quiero que el código/ciclo de prueba para estas clases sea rápido y vea el efecto de mis cambios rápidamente, algunas veces por minuto. MainTest, que es estable, tarda unos 20 segundos en cargarse, lo que no sería un problema si no hubiera necesitado volver a cargarlo para cada cambio en las clases en las que crea una instancia. Quiero cargar MainTest una vez, y cuando crea otra clase, llamémoslo ChildTest, en numerosas ocasiones (tras el evento del botón), debería volver a cargar la versión más reciente de ChildTest.

La pregunta en resumen:
¿Cómo se le dice al comando java 'new' que vuelva a cargar la clase desde el disco y no desde el caché de JVM?


Intenté Class.ForName pero no hizo la diferencia.
También he intentado utilizar un cargador de clases personalizado (copiado de código abierto), en vano.¿Cómo forzar a Java a volver a cargar la clase al crear instancias?

+1

¿está utilizando un marco de prueba como JUnit o es un banco de pruebas de cosecha propia? –

+0

clase de prueba de cosecha propia. – nat101

Respuesta

25

No hay ninguna posibilidad de "sobrecargar" al operador new, pero ciertamente podría escribir un cargador de clases personalizado que simplemente vuelva a cargar el bytecode cada vez que le pida que cargue una clase. Ningún cargador de clases listo para usar hará lo que está buscando porque todos suponen que la definición de clase no cambiará a lo largo de la vida de la JVM.

Pero así es cómo lo haces realidad. Cree un cargador de clases llamado, por ejemplo, Reloader que anula los métodos loadClass y findClass para que simplemente recarguen los archivos de clase del disco cada vez que son llamados (en lugar de "almacenarlos en caché" para usarlos posteriormente). Luego solo tiene que llamar al new Reloader().loadClass("foo.bar.MyClassName") cada vez que sospeche que la definición de la clase ha cambiado (por ejemplo, como parte de los métodos del ciclo de vida de su sistema de prueba).

This article rellena algunos de los detalles pero omite algunos puntos importantes, especialmente sobre el uso de nuevas instancias del cargador de clases para las recargas posteriores y la delegación al cargador de clases predeterminado cuando corresponda. Aquí es un simple ejemplo de trabajo que carga en repetidas ocasiones la clase MyClass y asume su archivo de clase existe en el directorio relativa "./bin":

public class Reloader extends ClassLoader { 
    public static void main(String[] args) throws Exception { 
     do { 
      Object foo = new Reloader().loadClass("MyFoo").newInstance(); 
      System.out.println("LOADED: " + foo); // Overload MyFoo#toString() for effect 
      System.out.println("Press <ENTER> when MyFoo.class has changed"); 
      System.in.read(); 
     } while (true); 
    } 

    @Override 
    public Class<?> loadClass(String s) { 
     return findClass(s); 
    } 

    @Override 
    public Class<?> findClass(String s) { 
     try { 
      byte[] bytes = loadClassData(s); 
      return defineClass(s, bytes, 0, bytes.length); 
     } catch (IOException ioe) { 
      try { 
       return super.loadClass(s); 
      } catch (ClassNotFoundException ignore) { } 
      ioe.printStackTrace(System.out); 
      return null; 
     } 
    } 

    private byte[] loadClassData(String className) throws IOException { 
     File f = new File("bin/" + className.replaceAll("\\.", "/") + ".class"); 
     int size = (int) f.length(); 
     byte buff[] = new byte[size]; 
     FileInputStream fis = new FileInputStream(f); 
     DataInputStream dis = new DataInputStream(fis); 
     dis.readFully(buff); 
     dis.close(); 
     return buff; 
    } 
} 

En cada invocación del 'do/while' bloque en el principal método , se crea una instancia de un nuevo Reloader que carga la clase desde el disco y la devuelve a la persona que llama. Por lo tanto, si sobrescribe el archivo bin/MyClass.class para contener una nueva implementación con un método diferente y sobrecargado toString, entonces debería ver la nueva implementación cada vez.

+0

Actualmente estoy trabajando con el CustomClassLoader de ese artículo, pero tengo problemas para lograr que haga lo que quiero. Suena como el enfoque correcto, pero tendré que profundizar en el artículo, supongo. – nat101

+0

Tendrá que tener cuidado de no utilizar el operador "nuevo" para la clase cambiante o de lo contrario se usará el cargador de clases del sistema predeterminado en lugar del suyo. Intente utilizar algo como esto en su lugar 'MyClass foo = myReloader.loadClass (" MyClass "). NewInstance();' – maerics

+0

¡Sí! Hace el trabajo. Incluso para una GUI. Gracias. – nat101

0

No estoy seguro de cómo hacer esto en el código, pero NetBeans tiene un botón "Aplicar cambios de código" que hará esto.

Sin embargo, solo puede cambiar la implementación de la clase, no puede cambiar su firma. Esto significa que no puede agregar, eliminar o cambiar variables o métodos de instancia. Esto se debe al diseño de la JVM que no permite que se cambien una vez que una clase se ha cargado una vez.

Como NetBeans puede hacerlo, debe haber una forma, pero no sé cómo hacerlo manualmente.

Si ejecuta un IDE que admita esta función, puede hacer clic en el botón cada vez que modifique una clase. Luego recompilará esa clase y la volverá a cargar.

+0

Estoy usando Eclipse, que se compila automáticamente al guardar. La pregunta es cómo cargar la clase recién compilada en un jvm que ya tiene cargada la versión anterior de la misma clase. – nat101

1

Parece que no quiere utilizar la implementación activa cuando se puede usar con el depurador. Cuando depura un problema y recompone algunas de sus clases, puede obtener la opción de volver a cargar las clases modificadas.

EDITAR: Además de usar la API de depuración, puede usar Instrumentación. http://download-llnw.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html

Sin embargo, dado que el uso de un depurador es, de lejos, la forma más sencilla de hacerlo, si esto no funciona, es probable que se encuentre con los mismos problemas.

Suena como lo que necesita para probar piezas de trabajo más pequeñas por lo que lleva menos de un segundo ejecutar algún subconjunto de su aplicación.

O puede cargar su aplicación más rápido al proporcionar una instalación de volcado y recarga para la memoria. De esta manera podría iniciar su aplicación desde una instantánea (quizás de inmediato)

+1

Realmente no puedo usar el depurador para este propósito, demasiado lento. (GUI extenso.) Pero mi pregunta es, si el depurador puede hacerlo, ¿por qué no puedo? – nat101

+0

Puede hacerlo si usa la API de depuración para hacerlo. Sin embargo, esto no es simple de usar. Cambiar el cargador de clases no cambiará una clase ya cargada cambiando su código. ver editar. –

1

Suena un poco aterrador, pero esto debería ayudar.

http://download.oracle.com/javase/1.4.2/docs/api/java/lang/ClassLoader.html

ClassLoader puede cargar dinámicamente las clases en tiempo de ejecución, me gustaría leer la API para determinar si la carga de nuevo reemplaza la versión anterior.

+1

Tendría que escribir mi propia subclase ClassLoader para lograr esto. – nat101

+0

Esto no sería muy difícil de lograr, simplemente extienda el ClassLoader y elija siempre crear una nueva instancia a partir del archivo fuente. La API proporciona una breve descripción de cómo podría hacer esto para obtener dinámicamente un archivo de una red. Su caso sería de una fuente de archivo constante. – Valchris

1

Puede probar Java Rebel. Es un cargador de clases "solo para el desarrollo" diseñado para hacer exactamente lo que necesita pero, quizás, podría ser costoso para sus necesidades. En tu caso, la solución de Peter Lawrey podría ser suficiente.

Cuestiones relacionadas