Para obtener una comprensión más profunda del cargador de clases, primero debe saber para qué se usa ClassLoader. Como su nombre lo indica, se utiliza para cargar archivos de clase en el JVM para usarlo por el programa. Sabemos que los programas Java pueden cargar dinámicamente las definiciones de clase, y este mecanismo de carga dinámica se implementa a través del cargador de clases, por lo que puede imaginar cuán importante es el cargador de clases.
Después de ver esto, algunos amigos pueden pensar en una pregunta, es decir, ya que el cargador de clases se usa para cargar clases en el JVM, ¿cómo se carga ClassLoader? ¿No es una clase de Java?
Así es, de hecho, hay un cargador de clases aquí que no está escrito en lenguaje Java, sino que es parte de la implementación de JVM. Este cargador de clases es Bootstrap ClassLoader (Start Class Loader), este cargador de clases carga la API de Java Core cuando el JVM se está ejecutando para satisfacer las necesidades más básicas del programa Java, incluido el cargador de clases definido por el usuario. El llamado cargador de clases definido por el usuario aquí se refiere al cargador de clases implementado a través del programa Java. Uno es el carácter extensor. Este cargador de clases se utiliza para cargar la API de extensión Java, es decir, la clase en /lib /ext, y la otra es el appClassLoader. Este cargador de clases se utiliza para cargar la clase en el directorio de configuración de classpath en la máquina del usuario. Por lo general, sin especificar el cargador de clases, las clases personalizadas del programador son cargadas por el cargador de clases.
Al ejecutar un programa, el JVM inicia y ejecuta Bootstrap ClassLoader. El cargador de clases carga la API Java Core (ExtClassLoader y AppClassLoader también se cargan en este momento), luego llama a ExtClassLoader para cargar la API de Extensión, y finalmente AppClassLoader carga la clase definida en el directorio ClassPath. Este es el proceso de carga más básico de un programa.
Lo anterior explica brevemente el papel del cargador de clases y el proceso de carga más básico. A continuación, explicaremos la forma en que se carga de cargadores de clases. Aquí tenemos que hablar sobre el uso del modo de delegado matriz para la carga de clase.
Cada cargador de clases personalizado debe heredar el cargador de clases abstracto, y cada cargador de clases tendrá un cargador de clases principal. Podemos ver que hay un método getParent () en el cargador de clases abstracto, que se utiliza para devolver al padre del cargador de clases actual. Tenga en cuenta que este padre no se refiere a la clase hereditaria, sino a un cargador de clases especificado al instanciar el cargador de clases. Si este padre es nulo, entonces el padre predeterminado del cargador de clases es el cargador de clases Bootstrap. ¿De qué sirve este padre?
Podemos considerar esta situación. Supongamos que hemos personalizado un ClientDefClassLoader y usamos este cargador de clases personalizado para cargar java.lang.string, ¿entonces este cargador de clases cargará la cadena? De hecho, la clase Java.lang.String no está cargada por ClientDefClassLoader, sino que está cargada por Bootstrap ClassLoader. ¿Por qué está sucediendo esto? De hecho, esta es la razón del modo delegado principal, porque antes de que cualquier cargador de clases personalizado cargue una clase, primero delegará a su cargador de clases de padre para cargar. Solo se cargará por sí mismo después de que el cargador de clases de Padre no se pueda cargar con éxito. En el ejemplo anterior, debido a que java.lang.string es una clase que pertenece a la API de Java Core, por lo que cuando se usa ClientDefClassLoader para cargarlo, el cargador de clases primero delegará a su cargador de clases paterna para cargar. Como se mencionó anteriormente, cuando el padre de ClassLoader es nulo, el padre de ClassLoader es el cargador de clases Bootstrap, por lo que en el nivel superior de ClassLoader está el cargador de clases Bootstrap, por lo que finalmente delegará a Bootstrap cuando el cargador de clases está presente, el Bootstrap ClassLoader devolverá la clase String.
Echemos un vistazo a una pieza de código fuente en el cargador de clases:
La clase de carga de clase sincronizada protegida (nombre de cadena, resolución booleana) lanza ClassNotFoundException {// Primero verifique si la clase especificada por el nombre está cargada Clase C = FindLoLoadClass (nombre); if (c == null) {try {if (parent! = null) {// Si el parent no es nulo, llame a la clase de carga de los padres a load = parent.loadClass (nombre, falso); } else {// Parent es nulo, llame a BootstrapClassLoader para cargar c = findbootstrapClass0 (nombre); }} Catch (ClassNotFoundException e) {// Si la carga aún no es exitosa, llame a su propio FindClass para cargar c = findclass (nombre); }} if (resolve) {resolveclass (c); } return c; } En el código anterior, podemos ver que el proceso general de cargar una clase es el mismo que el ejemplo que di antes. Cuando queremos implementar una clase personalizada, solo necesitamos implementar el método FindClass.
¿Por qué usar este modelo de delegación matriz?
La primera razón es que esto puede evitar la carga repetida. Cuando el padre ha cargado la clase, no hay necesidad de que el cargador de clases infantil lo cargue nuevamente.
La segunda razón es considerar factores de seguridad . Imaginemos que si no usamos este modo delegado, podemos reemplazar dinámicamente los tipos definidos en la API de Java Core en cualquier momento, lo que representará un riesgo de seguridad muy grande. El método del delegado principal puede evitar esta situación, porque la cadena ya está cargada al inicio, por lo que la clase definida por el usuario no puede cargar un cargador de clases personalizado.
Lo anterior es una breve introducción al mecanismo de carga del cargador de clases . A continuación, tengo que explicar otra clase relacionada con el cargador de clases, es decir, la clase de clase. El programador de cada archivo cargado por el cargador de clases es referenciado por el programador como una instancia de la clase de clase. Podemos tratar la clase de clase como una plantilla de la clase ordinaria. El JVM genera instancias correspondientes basadas en esta plantilla y finalmente es utilizado por el programador.
Vemos que hay un fórname de método estático en la clase de clase. Este método es el mismo que el método LoadClass en ClassLoader. Se usa para cargar clase, pero los dos tienen diferentes funciones.
Clase <?> LoadClass (nombre de cadena)
Clase <?> LoadClass (nombre de cadena, resolución booleana)
Vemos las dos declaraciones de métodos anteriores. El segundo parámetro del segundo método se utiliza para establecer si se puede conectar la clase al cargar la clase. Si es cierto, estará conectado, de lo contrario no estará conectado.
Hablando de conexión, tengo que explicarlo aquí. Al cargar una clase por JVM, es necesario pasar por tres pasos: cargar, conectar e inicializar. Cargar significa encontrar el archivo de clase correspondiente, leerlo en el JVM e inicializarlo debe discutirse, lo más importante es hablar sobre la conexión.
La conexión se divide en tres pasos. El primer paso es verificar si la clase cumple con las especificaciones. El segundo paso es la preparación. Es para asignar memoria para las variables de clase y establecer el valor inicial predeterminado. El tercer paso es la explicación. Este paso es opcional. De acuerdo con el segundo parámetro del método LoadClass anterior, se determina si se necesita una explicación. La llamada explicación se basa en la definición del libro "JVM en profundidad", que es encontrar la entidad correspondiente basada en las referencias de símbolos en la clase, y luego reemplazar la referencia del símbolo con una referencia directa. Es un poco profundo, jaja, no lo explicaré aquí. Si desea saber más, lea "JVM en profundidad". Jaja, si continúa explicándolo paso a paso, no sabrá cuándo estará terminado.
Echemos un vistazo al método LoadClass con dos parámetros. En el documento API Java, la definición de este método está protegida, lo que significa que el método está protegido, y el método que los usuarios realmente deben usar es el que tiene un parámetro. El método LoadClass de un parámetro realmente llama al método con dos parámetros, y el segundo parámetro predetermina a False. Por lo tanto, se puede ver aquí que la clase de carga a través de LoadClass en realidad no se explica al cargar, por lo que la clase no se inicializará. El método fornido de la clase de clase es lo contrario. Al cargar con Forname, la clase se explicará e inicializará. FORNAME también tiene otra versión del método, que puede establecer si inicializar y establecer clases de cargador. No hablaré más de eso aquí.
Me pregunto si la explicación de estos dos métodos de carga es lo suficientemente claro. Demos un ejemplo aquí. Por ejemplo, al cargar el controlador JDBC , usamos el ForName en lugar del método LoadClass de ClassLoader al cargar controladores JDBC? Sabemos que el controlador JDBC está a través del Drivermanager y debe estar registrado en el Drivermanager. Si la clase de controlador no se inicializa, no se puede registrar en el Drivermanager. Por lo tanto, se debe usar FORNAME en lugar de LoadClass.
A través del cargador de clases, podemos personalizar el cargador de clases y personalizar el método de carga que necesitamos, como la carga desde la red, cargar desde otros formatos de archivos, etc. De hecho, todavía hay muchas cosas que no se han mencionado en el cargador de clases, como algunas implementaciones dentro de la clase cargadora, etc.
A través de este artículo, el editor espera que todos comprendan el mecanismo de cargadores de clases, ¡gracias por su apoyo!