Buscar..


Observaciones

Un cargador de clases es una clase cuyo propósito principal es mediar la ubicación y carga de las clases utilizadas por una aplicación. Un cargador de clases también puede encontrar y cargar recursos .

Las clases estándar del cargador de clases pueden cargar clases y recursos de directorios en el sistema de archivos y de archivos JAR y ZIP. También pueden descargar y almacenar en caché los archivos JAR y ZIP desde un servidor remoto.

Los cargadores de clases normalmente están encadenados, de modo que la JVM intentará cargar las clases de las bibliotecas de clases estándar con preferencia a las fuentes proporcionadas por la aplicación. Los cargadores de clases personalizados permiten que el programador altere esto. También puede hacer cosas como descifrar archivos de bytecode y modificación de bytecode.

Instalar y usar un cargador de clases

Este ejemplo básico muestra cómo una aplicación puede instanciar un cargador de clases y usarlo para cargar dinámicamente una clase.

URL[] urls = new URL[] {new URL("file:/home/me/extras.jar")};
Classloader loader = new URLClassLoader(urls);
Class<?> myObjectClass = loader.findClass("com.example.MyObject");

El cargador de clases creado en este ejemplo tendrá el cargador de clases predeterminado como principal, y primero intentará encontrar cualquier clase en el cargador de clases principal antes de buscar en "extra.jar". Si la clase solicitada ya se ha cargado, la llamada findClass devolverá la referencia a la clase cargada anteriormente.

La llamada a findClass puede fallar de varias maneras. Los más comunes son:

  • Si no se puede encontrar la clase nombrada, la llamada con la ClassNotFoundException .
  • Si la clase nombrada depende de alguna otra clase que no se puede encontrar, la llamada lanzará NoClassDefFoundError .

Implementando un classLoader personalizado

Cada cargador personalizado debe extender directa o indirectamente la clase java.lang.ClassLoader . Los principales puntos de extensión son los siguientes métodos:

  • findClass(String) : sobrecargue este método si su cargador de clases sigue el modelo de delegación estándar para la carga de clases.
  • loadClass(String, boolean) : sobrecargue este método para implementar un modelo de delegación alternativo.
  • findResource y findResources : sobrecargue estos métodos para personalizar la carga de recursos.

Los métodos defineClass que son responsables de cargar realmente la clase desde una matriz de bytes son final para evitar la sobrecarga. Cualquier comportamiento personalizado debe realizarse antes de llamar a defineClass .

Aquí hay un sencillo que carga una clase específica de una matriz de bytes:

public class ByteArrayClassLoader extends ClassLoader {
    private String classname;
    private byte[] classfile;

    public ByteArrayClassLoader(String classname, byte[] classfile) {
        this.classname = classname;
        this.classfile = classfile.clone();
    }

    @Override
    protected Class findClass(String classname) throws ClassNotFoundException {
        if (classname.equals(this.classname)) {
            return defineClass(classname, classfile, 0, classfile.length);
        } else {
            throw new ClassNotFoundException(classname);
        }
    }
}

Dado que solo hemos anulado el método findClass , este cargador de clases personalizado se comportará de la siguiente manera cuando se llame a loadClass .

  1. El método loadClass del cargador de clases llama a findLoadedClass para ver si este cargador de clases ya ha cargado una clase con este nombre. Si eso tiene éxito, el objeto Class resultante se devuelve al solicitante.
  2. El método loadClass luego delega al cargador de clases principal llamando a su llamada loadClass . Si el padre puede manejar la solicitud, devolverá un objeto de Class que luego se devolverá al solicitante.
  3. Si el cargador de clases principal no puede cargar la clase, findClass luego llama a nuestro método findClass , pasando el nombre de la clase a cargar.
  4. Si el nombre solicitado coincide con this.classname , llamamos a defineClass para cargar la clase real de la this.classfile bytes this.classfile . El objeto de Class resultante se devuelve.
  5. Si el nombre no coincide, lanzamos la ClassNotFoundException .

Cargando un archivo .class externo

Para cargar una clase primero necesitamos definirla. La clase está definida por el ClassLoader . Solo hay un problema, Oracle no escribió el código del ClassLoader con esta característica disponible. Para definir la clase, necesitaremos acceder a un método llamado defineClass() que es un método privado del ClassLoader .

Para acceder a él, lo que haremos es crear una nueva clase, ByteClassLoader , y extenderla a ClassLoader . Ahora que hemos extendido nuestra clase a ClassLoader , podemos acceder a los métodos privados de ClassLoader . Para hacer que defineClass() esté disponible, crearemos un nuevo método que actuará como un espejo para el método defineClass() privado. Para llamar al método privado necesitaremos el nombre de la clase, name , los bytes de clase, classBytes , offset del primer byte, que será 0 porque classBytes datos 'comienza en classBytes[0] , y offset del último byte, que será classBytes.lenght porque representa el tamaño de los datos, que será el último desplazamiento.

public class ByteClassLoader extends ClassLoader {

    public Class<?> defineClass(String name, byte[] classBytes) {
        return defineClass(name, classBytes, 0, classBytes.length);
    }

}

Ahora, tenemos un defineClass() público defineClass() . Puede invocarse pasando el nombre de la clase y los bytes de la clase como argumentos.

Digamos que tenemos una clase llamada MyClass en el paquete stackoverflow ...

Para llamar al método, necesitamos los bytes de la clase, así que creamos un objeto Path que representa la ruta de nuestra clase usando el método Paths.get() y pasando la ruta de la clase binaria como un argumento. Ahora, podemos obtener los bytes de la clase con Files.readAllBytes(path) . Así que creamos una instancia de ByteClassLoader y usamos el método que creamos, defineClass() . Ya tenemos los bytes de clase, pero para llamar a nuestro método también necesitamos el nombre de clase que viene dado por el nombre del paquete (punto) el nombre canónico de clase, en este caso stackoverflow.MyClass .

Path path = Paths.get("MyClass.class");

ByteClassLoader loader = new ByteClassLoader();
loader.defineClass("stackoverflow.MyClass", Files.readAllBytes(path);

Nota : el método defineClass() devuelve un objeto Class<?> . Puedes guardarlo si quieres.

Para cargar la clase, solo llamamos a loadClass() y pasamos el nombre de la clase. Este método puede lanzar una ClassNotFoundException por lo que necesitamos usar un bloque cat de prueba

try{
    loader.loadClass("stackoverflow.MyClass");
} catch(ClassNotFoundException e){
    e.printStackTrace();
}


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow