Modificando el classpath dinamicamente en tiempo de ejecución

12Ago08

Cada vez que se necesita usar una clase determinada durante la ejecución de un programa la JVM llama a su cargador de clases para que este la busque y la deje disponible (la cargue en memoria).

El cargador de clases que la maquina virtual utiliza por defecto es una instancia de una subclase de URLClassLoader (y por ende subclase de ClassLoader también). El funcionamiento básico del URLClassLoader es buscar las clases dentro del listado de URLs que posee registrado. A este listado de URLs es lo que conocemos como classpath.

Dentro del classpath podemos encontrarnos con los siguientes tipos de URLs:

  • URL hacia un directorio (ruta terminada en ‘/’): En este caso se incluyen en el classpath todos los archivos .class que se encuentren dentro del directorio
  • URL a un JAR (ruta terminada con “.jar”): En este caso se incluyen al classpath todos los archivos .class que se encuentren dentro del archivo
  • URL a un JAR (ruta no termina en ‘/’ o “.jar”): Se presume que es un jar por lo que antes de procesarla se le concatena “.jar”. Después se trata igual que el caso anterior.

Por defecto encontramos en el classpath al ejecutar una aplicación solo el JAR o directorio que contiene la clase con el método Main. Para ver esto podemos ejecutar el siguiente código:

	System.out.println("ClassPath:");
	for (URL url : ((URLClassLoader) ClassLoader.getSystemClassLoader()).getURLs()) {
		System.out.println("\t" + url.getFile());
	}

Lo que hacemos en este ejemplo es tomar el cargador de clases del sistema, castearlo a URLClassLoader, obtener el listado de URLs, y a cada URL la mostramos por la consola.

Agregar nuevas rutas estáticas al classpath es simple, Existen dos formas:

  • agregar la opción -cp seguido de las librerías que deseamos incluir (separadas por “;”) al ejecutar el comando java:

java -cp unJar.jar;otroJar.jar;unDirectorio/ claseInicial

  • otra forma es si vamos a ejecutar un JAR es incluir el nombre de las librerías en el manifiesto del archivo bajo la propiedad “Class-Path”, separando cada clase por espacio:

Class-Path: unJar.jar otroJar.jar unDirectorio/

Pero si lo que queremos es agregar nuevas rutas en tiempo de ejecución (porque solo durante la ejecución del programa podemos determinar que archivos necesitamos cargar) deberemos apelar al cargador de clases, que gracias a la interfase URLClassLoader posee un método addURL. El problema básico que nos encontramos ahora es que es un método declarado como protegido, por lo cual para poder utilizarlo deberemos apelar a reflexión. Por ejemplo:

	URL url = (new File("algunJar.jar")).toURI().toURL();

	Method metodo = URLClassLoader.class.getDeclaredMethod(
		"addURL", new Class[]{URL.class});
	metodo.setAccessible(true);
	metodo.invoke((URLClassLoader) ClassLoader.getSystemClassLoader()
		, new Object[]{url});

Donde básicamente para agregar la ruta al archivo “algunJar.jar” debemos obtener el método addURL con un parámetro del tipo URL y guardando una referencia en la variable “metodo”, lo habilitamos para poder invocarlo, y finalmente lo invocamos en el cargador de clases del sistema usando como parámetro la URL proveniente del archivo a agregar (URL que guardamos en la variable “url” en la primera linea)

Podemos preparar una clase que nos permita agregar facilmente ya sean URLs, archivos (File) o con solo el nombre del archivo:

	package ar.lefunes.classpath;

	import java.io.File;
	import java.lang.reflect.Method;
	import java.net.MalformedURLException;
	import java.net.URL;
	import java.net.URLClassLoader;

	public class ModificadorClassPath {

	    private static final String METODO_ADD_URL = "addURL";
	    private static final Class[] PARAMETRO_METODO = new Class[]{URL.class};
	    private final URLClassLoader loader;
	    private final Method metodoAdd;

	    public ModificadorClassPath() throws NoSuchMethodException {
	        loader = (URLClassLoader) ClassLoader.getSystemClassLoader();
	        metodoAdd = URLClassLoader.class.getDeclaredMethod(
                                           METODO_ADD_URL, PARAMETRO_METODO);
	        metodoAdd.setAccessible(true);
	    }

	    public URL[] getURLs() {
	        return loader.getURLs();
	    }

	    public void addURL(URL url) {
	        if (url != null) {
	            try {
	                metodoAdd.invoke(loader, new Object[]{url});
	            } catch (Exception ex) {
	                System.err.println("Excepcion al guardar URL: " + 
                                                               ex.getLocalizedMessage());
	            }
	        }
	    }

	    public void addURLs(URL[] urls) {
	        if (urls != null) {
	            for (URL url : urls) {
	                addURL(url);
	            }
	        }
	    }

	    public void addArchivo(File archivo) throws MalformedURLException {
	        if (archivo != null) {
	            addURL(archivo.toURI().toURL());
	        }
	    }

	    public void addArchivo(String nombreArchivo) throws MalformedURLException {
	        addArchivo(new File(nombreArchivo));
	    }
	}

Además con el método getURLs obtendremos el classpath actual de la aplicación.

Un ejemplo de uso sería:

	ModificadorClassPath mc = new ModificadorClassPath();

	mc.addArchivo("algunJar.jar");

	File unArchivo = new File("otroJar.jar");
	mc.addArchivo(unArchivo);

	String dir = "elPath" + File.separator + "unDirectorio" + File.separator;
	File unDirectorio = new File(dir);
	mc.addArchivo(unDirectorio);

	System.out.println("ClassPath:");
	for (URL url : mc.getURLs()) {
		System.out.println("\t" + url.getFile());
	}

Hasta la próxima
Saludos

Más Info



5 Responses to “Modificando el classpath dinamicamente en tiempo de ejecución”

  1. @ramiro simplemente para no tener problemas con intentar cargar clases que no tienes en el classpath utilizá un try que controle la excepción ClassNotFoundException:

    try{
      //codigo que carga las clases
    } catch( ClassNotFoundException e){
      System.err.println("Clase no encontrada:" + e.getMessage());
    }
    
  2. 2 ramiro

    muy buenas, antes de nada muxas gracias por el articulo, la verdad que me ha sido de gran ayuda, te voy a contar mi caso.
    necesito hacer una linea de productos en java, para ello necesito cargar paquetes en tiempo de ejecucion, xo tambien necesito q si se va a acceder a paquetes que no estan incluidos en el classpath no deje de funcionar la aplicacion.
    la cosa esq yo desde la interface podria acceder a clases de paquetes no incluidos, y quiero controlar que eso no destruya toda la aplicacion.

    no sabes cuanto agradeceria tu ayuda.

    un saludo, y nuevamente muchas gracias

  3. @Diego Tal como dices son las dos formas de realizar esta tarea y son igualmente válidas. En mi caso particular siempre me resulto más comodo realizarlo por este camino.
    Si el tiempo me acompaña (sobre todo el tiempo libre) escribiré algo al respecto.
    Saludos (y perdona la demora :P)

  4. 4 Diego

    No podrias haber obtenido el mismo resultado extendiendo la clase URLClassLoader en vez de llamar al metodo por refection?
    Saludos


  1. 1 Creando un sistema con plugins en Java « Le Funes

A %d blogueros les gusta esto: