Java Language
Cargadores de clases
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
yfindResources
: 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
.
- El método
loadClass
del cargador de clases llama afindLoadedClass
para ver si este cargador de clases ya ha cargado una clase con este nombre. Si eso tiene éxito, el objetoClass
resultante se devuelve al solicitante. - El método
loadClass
luego delega al cargador de clases principal llamando a su llamadaloadClass
. Si el padre puede manejar la solicitud, devolverá un objeto deClass
que luego se devolverá al solicitante. - Si el cargador de clases principal no puede cargar la clase,
findClass
luego llama a nuestro métodofindClass
, pasando el nombre de la clase a cargar. - Si el nombre solicitado coincide con
this.classname
, llamamos adefineClass
para cargar la clase real de lathis.classfile
bytesthis.classfile
. El objeto deClass
resultante se devuelve. - 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();
}