Cambiar el classpath en tiempo de ejecución

De ChuWiki

A veces puede interesarnos cambiar el classpath dinámicamente, mientras se está ejecutando nuestro programa. Aquí vamos a ver una forma de hacerlo, pero con un par de pegas que comentamos ahora:

  • Es posible que no funcione con máquinas virtuales de java que no sean la de SUN, puesto que presupone que el ClassLoader usado es o hereda de URLClassLoader.
  • Se va a hacer "trampa" para llamar a un método protegido. Si no es público, será por algo. De todas formas, aparentemente funciona.

Obtenemos el ClassLoader[editar]

En primer lugar obtenemos el ClassLoader del sistema que, como hemos comentado, vamos a presuponer que es un URLClassLoader. Esto es cierto en las máquinas virtuales de sun.

URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();

Obtenemos el método addURL()[editar]

Esta clase tiene un método addURL() que nos sirve para añadir un jar o un directorio al ClassLoader, pero ese método es protegido y no podemos llamarlo de forma normal. Aquí es donde hacemos una pequeña trampa consistente en usar la reflexión de java para invocar a ese método.

Obtenemos entonces, mediante reflexión, ese método addURL() que admite de parámetro una URL. Puesto que es protegido, para saltarnos esa protección, hacemos que el método sea "accesible"

Method metodoAdd = URLClassLoader.class.getDeclaredMethod("addURL",
                    new Class[] { URL.class });
metodoAdd.setAccessible(true);

Y lo invocamos[editar]

y ahora sólo nos queda invocarlo pasándole la URL de nuestro jar o directorio.

// La URL del jar que queremos añadir.
URL url = new URL("file:///C:/Users/chuidiang/lib/mysql-connector-java-5.1.6.jar");

// Se invoca al método
metodoAdd.invoke(classLoader,url);

y con esto debería ser suficiente para a partir de aquí, empezar a usar las clases de ese jar.


Un ejemplo completo[editar]

Aquí un ejemplo completo de código. Debes cambiar el path en el que tengas el driver de mysql o usar otra clase y otro jar.

package com.chuidiang.ejemplos.classloader;

import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * Ejemplo de cambio del classpath en tiempo de ejecucion. Este ejemplo puede no
 * funcionar en maquinas virtuales que no sean la de sun.
 * 
 * @author Chuidiang
 * 
 */
public class PruebaCambioClassPath {

    /**
     * El ejemplo.
     */
    public static void main(String[] args) {

        // Comprobamos que en el classpath no existe el driver de mysql.
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            System.out.println("no se encuentra com.mysql.jdbc.Driver");
        }

        // Cambiamos el classpath, anadiendo un nuevo jar al ClassLoader.
        try {
            // Se obtiene el ClassLoader y su metodo addURL()
            URLClassLoader classLoader = ((URLClassLoader) ClassLoader
                    .getSystemClassLoader());
            Method metodoAdd = URLClassLoader.class.getDeclaredMethod("addURL",
                    new Class[] { URL.class });
            metodoAdd.setAccessible(true);

            // La URL del jar que queremos anadir
            URL url = new URL(
                    "file:///C:/Users/chuidiang/lib/mysql-connector-java-5.1.6.jar");

            // Se invoca al metodo addURL pasando esa url del jar
            metodoAdd.invoke(classLoader, new Object[] { url });
        } catch (Exception e) {
            e.printStackTrace();
        }

        // Se comprueba que ahora si se puede acceder a esa clase.
        try {
            Class.forName("com.mysql.jdbc.Driver");
            System.out.println("ya se encuentra com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            System.out.println("Pues no, sigue sin estar accesible");
        }

    }
}