A partir del código fuente, lo llevaré a analizar la delegación parental de la carga de clases Java.

Java Como una de las características principales de Java, la carga de clases tiene muchas características. Comprender y dominar la forma y el mecanismo de carga de clases puede comprender mejor el proceso de ejecución del programa. Este artículo presentará en detalle el mecanismo de carga de clases de Java y su conocimiento necesario relacionado. .

Vayamos directo a ello

1. Compilación de archivos

Antes de presentar el cargador de clases, primero introduzca  Java los comandos básicos de compilación y ejecución del archivo para su uso posterior.

1. Compilación de clases

  • compilación de archivos

     Compile el archivo en  un archivo mediante  javac el comando   y envíelo al directorio actual de forma predeterminada..java.class

    intento

    copiar código

    javac MyBean.java
  • proceso de codificación

    Cuando  Java el archivo contiene codificaciones como el chino, debe especificar el formato de codificación al compilar; de lo contrario, la compilación será anormal.

    intento

    copiar código

    javac -encoding utf-8 MyBean.java
  • Procesamiento de nombres de paquetes

    El archivo compilado por el comando predeterminado  javac no contiene la ruta del paquete, y  -d el archivo generado por la compilación de parámetros creará una carpeta jerárquica de nombres de paquetes en el mismo directorio de nivel. Significa  . guardar en el directorio actual, y el directorio se puede especificar según sea necesario.

    intento

    copiar código

    javac -d . MyBean.java
  • versión especificada

    Cuando hay varias  JDK versiones en el entorno de programación, puede especificar la versión compilada ingresando la ruta completa  JDK .

    intento

    copiar código

    "C:\Program Files\Java\jdk1.8.0_202\bin\javac" -d <target_path> MyBean.java

2. Ejecución de archivos

  • Operación de clase

    Class Es relativamente simple ejecutar el archivo, simplemente pase  java +  directamente 文件名 , Cabe señalar que si el nombre del paquete se establece al compilar el archivo, el nombre del paquete también debe especificarse al ejecutar.

    intento

    copiar código

    # 运行不含包名 class 文件 java MyBean # 运行含包名 class 文件 java xyz.ibudai.MyBean
  • ejecutar jarra

    Ejecutar  Jar un archivo es similar a ejecutar  Class un archivo, solo agregue  -jar parámetros.

    intento

    copiar código

    java -jar jar-name.jar

Segundo, el cargador de clases.

1. Categorías básicas

Java Los cargadores de clases en  China se dividen en las siguientes cuatro categorías y las descripciones de cada tipo de cargador son las siguientes:

Cargador describir
Cargador de clases Bootstrap El cargador de nivel superior es el principal responsable de cargar las bibliotecas de clases principales (java.lang.*, etc.).
Cargador de clases ext Es el principal responsable de cargar algunos paquetes JAR extendidos en el directorio jre/lib/ext.
Cargador de clases de aplicaciones Es el principal responsable de cargar la función principal del programa de aplicación, etc.
Cargador de clases personalizado El cargador de clases personalizado, es decir, heredamos ClassLoad y el cargador personalizado pertenece al cargador inferior.

2. Método de carga

 Hay  dos formas Java de cargar clases en   que se presentan a continuación.Class.forName()Classloader.laodClass()

  • Clase.paraNombre()

    La carga por  Class.forName() método no solo cargará la clase actual en la memoria, sino que también inicializará el objeto. Puede agregar un  static mensaje de impresión de bloque estático a la clase que se va a cargar. Cuando use  Class.forName() la clase cargada, puede encontrar que el código de bloque estático será ejecutado, indicando que se crea en este momento.

    Si desea implementar la carga de clases sin crear objetos, puede usar  Class.forName(className, false, classLoader) la carga de métodos, donde el segundo parámetro se usa para especificar si se crean objetos y el tercer parámetro especifica el cargador de clases.

    Java

    copiar código

    public void initDemo2() { String className = "xyz.ibudai.bean.User"; ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); try { Class<?> clazz1 = Class.forName(className); Class<?> clazz2 = Class.forName(className, false, classLoader); System.out.println("clazz1 loader: " + clazz1.getClassLoader()); System.out.println("clazz2 loader: " + clazz2.getClassLoader()); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
  • Cargador de clases.laodClass()

    La carga por  laodClass() medios no resuelve la clase, solo implementa la carga y no crea el objeto correspondiente, y solo se inicializa cuando se hace referencia al objeto.

    Java

    copiar código

    public void initDemo2() { String className = "xyz.ibudai.bean.User"; ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); try { Class<?> clazz3 = classLoader.loadClass(className); System.out.println("clazz3 loader: " + clazz3.getClassLoader()); } catch (Exception e) { e.printStackTrace(); } }

Cabe señalar  java.lang.String que la biblioteca de clases principales se  JVM carga al inicio, se encuentran  Java en la biblioteca de clases principales de la máquina virtual y se  Bootstrap ClassLoader cargan mediante . Por lo tanto al obtener los cargadores de clases de estas clases el resultado suele ser  null , cabe señalar que  null el cargador de clases es desconocido o no se puede determinar, en lugar de que no existe ningún cargador de clases.

3. Mecanismo de carga de clases

1. Mecanismo de carga

El cargador de clases es responsable de cargar el código de bytes de clases de diferentes fuentes (como sistema de archivos, red, memoria, etc.), como los  .class archivos más comunes, y convertirlos en  Java objetos para que los programas puedan usar estas clases.

El cargador de clases también es responsable de resolver las dependencias de clases, es decir, encontrar y cargar otras clases de las que depende la clase actual y determinar qué cargador de clases debe cargar cada clase.

Java El mecanismo de carga de clases tiene las siguientes características:

  • Carga diferida : una clase solo se carga cuando es necesario para ahorrar memoria y tiempo de carga.
  • Delegación parental : el cargador de clases cargará las clases de acuerdo con la jerarquía, es decir, primero confiará la carga al cargador de clases principal, y si el cargador de clases principal no puede cargar, luego se lo entregará a sí mismo para que lo cargue.
  • Mecanismo de almacenamiento en caché : las clases que ya se han cargado se almacenarán en caché para evitar cargar la misma clase repetidamente.
  • Destruya el mecanismo de delegación principal : permita a los usuarios personalizar el cargador de clases para cargar clases, de modo que se puedan implementar algunas estrategias de carga de clases personalizadas, como implementación en caliente, complementos, etc.

2. Proceso de carga

El llamado proceso de carga de clases, en términos simples, el  class archivo compilado se carga en la memoria de la máquina virtual de una manera específica  JVM y luego debería poder leerse en la memoria de la máquina virtual.

El cargador de clases pasa principalmente por tres etapas para cargar archivos de código de bytes en la memoria  加载 -> 连接 -> 实例化.

Tenga en cuenta que no se  .class carga en la memoria de una vez, sino  Java sólo cuando la aplicación lo necesita. Es decir, cuando  JVM se solicita cargar una clase, el cargador de clases intentará encontrar y localizar la clase y, después de encontrar la clase correspondiente, cargará su definición de clase completa en el área de datos de tiempo de ejecución.

4. Designación por los padres

El mecanismo de delegación principal es un tipo de mecanismo de carga muy importante en la carga de clases, a través del cual se garantiza la seguridad y la integridad de la clase.

JDK El mecanismo de delegación parental se utiliza de forma predeterminada. Puede  encontrar  java.lang la  ClassLoader clase en el paquete. El siguiente es un análisis del mecanismo de delegación parental desde la perspectiva del código fuente.

1. Descripción del método

Lea la siguiente tabla antes de analizar el código fuente principal. Cada método se involucrará más adelante. Aquí hay una breve descripción de su función y no se describirá en detalle más adelante.

método efecto
getClassLoadingLock() Evita que las clases se carguen simultáneamente.
encontrarClaseCargada() Determine si la clase se ha cargado y devuelva el cargador si se ha cargado; de lo contrario, devuelva nulo.
encontrarBootstrapClassOrNull() Delega el cargador principal para que cargue; si no existe ningún cargador principal, devuelve nulo.
encontrarClase() Lea el archivo .class según el nombre completo de la clase y llame a defineClass() para encontrar el objeto java.lang.Class correspondiente.
definirClase() Convierta el flujo de bytes en un objeto java.lang.Class, el flujo de bytes puede provenir del archivo .class y de otras formas.
resolverClase() Los archivos de código de bytes se cargan en la JVM durante la carga de clases y no se convierten inmediatamente en objetos java.lang.Class. Solo cuando se use esta clase se llamará a resolveClass() para la conversión y se podrán realizar operaciones como la creación de una instancia.

2. Interpretación del código fuente.

El núcleo de la implementación de carga de clases es  loadClass() el método, que utiliza  synchronized palabras clave para garantizar que la clase no se cargue repetidamente. El código fuente es el siguiente  JDK y  loadClass() he proporcionado comentarios sobre los puntos clave.

Entre ellos  parent se encuentra el cargador principal del cargador actual.

 
 

Java

copiar código

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 1. 判断类是否已经加载过 Class<?> c = findLoadedClass(name); // 1.1 若不为空表明已加载则返回 if (c == null) { try { // 2. 未加载 -> 判断是否存在父加载器 if (parent != null) { // 2.1 存在 -> 递归调用直至获取顶层父加载器 c = parent.loadClass(name); } else { // 2.2 不存在(已达顶层加载器) -> 委托父类进行加载 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found } // 3. 没有父加载器可以加载类 -> 由类自身实现加载 if (c == null) { c = findClass(name); } } return c; } }

5. Carga personalizada

Hay dos tipos más comunes de cargadores de clases personalizados: seguir el mecanismo de delegación parental y romper el mecanismo de delegación parental. Este último se analiza principalmente aquí.

1. Herencia de clase principal

Cree una nueva clase de cargador personalizada  CustomClassLoader y heredéela  , y defina  información básica como ClassLoader el método de construcción y la ruta de almacenamiento de archivos predeterminada en la clase  .  Los y  classen el constructor   se utilizan para especificar el cargador principal del cargador actual. El primero obtiene de forma predeterminada el cargador de clases del contexto actual como cargador principal, en la mayoría de los casos   .super()super(parent)AppClassLoader

En aplicaciones prácticas, generalmente solo clases específicas necesitan implementar una carga personalizada, por lo que la colección se define aquí para  targetList almacenar las clases de destino que necesitan implementar una carga personalizada, y el método de carga predeterminado se usa para las clases que no están en esta colección.

 
 

Java

copiar código

public class CustomClassLoader extends ClassLoader { private final Path classDir; private static final Path DEFAULT_CLASS_DIR = Paths.get("E:\\Workspace\\Class"); private List<String> targetList = new ArrayList<>(); static { targetList.add("xyz.ibudai.bean.TestA"); targetList.add("xyz.ibudai.bean.TestB"); } /** * 使用默认的信息 */ public CustomClassLoader() { super(); this.classDir = DEFAULT_CLASS_DIR; } /** * 指定文件目录与父类加载器 */ public CustomClassLoader(String path, ClassLoader parent) { super(parent); this.classDir = Paths.get(path); } }

2. cargarClase()

El siguiente paso también es el foco de la carga de clases personalizadas.  JDK El proceso central de carga de una clase está  loadClass() controlado por un método. Si desea romper el mecanismo de delegación principal predeterminado, debe reescribir este método.

El proceso de implementación reescrito  loadClass() de su carga concreta es el siguiente:

  • ¿Comprueba si la clase actual ya se ha cargado?

    • Si está cargado, devuelve el resultado.
    • Si no está cargado, vaya al siguiente paso.
  • ¿Está la clase cargada en la colección de destino?

    • Si es así, implemente  .class la lectura y carga de archivos personalizados.
    • De lo contrario, llame  loadClass() al reintento de carga del cargador principal, es decir, siga el mecanismo de delegación principal.
  • ¿Determinar si la carga personalizada fue exitosa?

    • Si es así, devuelve el resultado.
    • De lo contrario, llame  loadClass() al reintento de carga del cargador principal, es decir, siga el mecanismo de delegación principal.

La implementación del código correspondiente al flujo lógico anterior es la siguiente:

 
 

Java

copiar código

@Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<?> c = findLoadedClass(name); if (c != null) { // 若已加载则返回 return c; } if(targetList.contains(name)) { try { // 若在目标集合则自定义加载 c = customFindClass(name); } catch (ClassNotFoundException ignored) { // 加载异常重新委派父类加载 c = super.loadClass(name, resolve); } } if (c == null) { // 加载不成功重新委派父类加载 c = super.loadClass(name, resolve); } return c; } }

3. buscarClase()

loadClass() Se utiliza para controlar el proceso de carga de la clase  en la carga de la clase  y findClass() se utiliza para controlar la  .class implementación específica de carga de archivos, es decir, cómo cargarlo  .class en el archivo  JVM .

Aquí elegí crear un nuevo  customFindClass() método en lugar de reescribirlo.  El propósito es evitar  confusiones y conflictos al llamar a la clase principal  cuando  findClass() la clase personalizada no se carga  .loadClass()findClass()

customFindClass() La carga del archivo de clase compilado se realiza en  JVM es decir, el  .class archivo compilado se lee desde el local y se convierte en datos de bytes y   se busca defineClass() el objeto correspondiente  .java.lang.Class

¿Dónde  name está el nombre de clase completo del archivo de clase, por ejemplo: xyz.ibudai.bean.TestA .

 
 

Java

copiar código

private Class<?> customFindClass(String name) throws ClassNotFoundException { // 读取 class 的二进制数据 byte[] classBytes = this.readClassBytes(name); if (classBytes == null || classBytes.length == 0) { throw new ClassNotFoundException("The class byte " + name + " is empty."); } // 调用 defineClass 方法定义 class return this.defineClass(name, classBytes, 0, classBytes.length); } /** * 将 class 文件转为字节数组以供后续内存载入 */ private byte[] readClassBytes(String name) throws ClassNotFoundException { // 将包名分符转换为文件路径分隔符 String classPath = name.replace(".", "/"); Path classFullPath = classDir.resolve(Paths.get(classPath + ".class")); if (!classFullPath.toFile().exists()) { throw new ClassNotFoundException("Class file " + classFullPath + " doesn't exists."); } try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { Files.copy(classFullPath, out); return out.toByteArray(); } catch (IOException e) { throw new ClassNotFoundException("Read class " + classFullPath + " file to byte error."); } }

4. Ejemplo de prueba

Después de completar el ejemplo anterior, verifique el efecto de la carga de nuestra clase a través de un ejemplo.

Cree una nueva clase de prueba  TestA y  java -d . TestA.java compílela en  .class un archivo mediante el comando, y mueva el  .class archivo compilado con la jerarquía de nombres del paquete al  CustomClassLoader directorio predeterminado definido en la clase.

 
 

Java

copiar código

public class TestA { public void sayHello() { System.out.println("Hello world!"); } }

CustomClassLoader Después de completar las operaciones anteriores, escriba el ejemplo de prueba correspondiente, realice  TestA la carga de la clase  a través de la carga de clase predeterminada y  dos métodos, y genere TestA la información de la clase de carga específica respectivamente.

El código de muestra de prueba correspondiente es el siguiente:

 
 

Java

copiar código

@Test public void loadTest() throws Exception { // 默认类加载器加载类 Class<?> clazz1 = Class.forName("xyz.ibudai.bean.TestA"); System.out.println("Loader-1: " + clazz1.getClassLoader()); // 自定义类加载器加载类 CustomClassLoader myLoader = new CustomClassLoader(); Thread.currentThread().setContextClassLoader(myLoader); Class<?> clazz2 = myLoader.loadClass("xyz.ibudai.bean.TestA"); System.out.println("Loader-2: " + clazz2.getClassLoader()); }

El resultado de ejecutar el programa anterior es el siguiente: a partir del resultado, podemos ver que  CustomClassLoader hemos implementado con éxito  TestA la carga personalizada.

 
 

Java

copiar código

Loader-1: sun.misc.Launcher$AppClassLoader@18b4aac2 Loader-2: xyz.ibudai.loader.CustomClassLoader@1b9e1916

6. Operación avanzada

1. Cargador de contexto

En el ejemplo anterior,  Thread.currentThread().getContextClassLoader() se pasó la carga de clases del contexto actual, entonces, ¿para qué sirve la carga de clases de contexto?

Cuando  Java se inicia el programa, el cliente cargará la clase principal  Bootstrap Classloader , pero hay algunas interfaces estándar que solo están definidas por la interfaz del proveedor de servicios  (SPI) . Por ejemplo, las implementaciones específicas más comunes  JDBC las implementan los principales fabricantes de bases de datos. Y esta parte de la clase de implementación de la interfaz obviamente no puede ser cargada por el cargador de clases del sistema (el cargador de clases del sistema solo cargará el paquete principal, no el paquete fuente de terceros), y el mecanismo de carga de clases predeterminado () no admite la 双亲委派机制cargador principal para delegar hacia abajo, el cargador de contexto nace para este propósito y  SPI rompe el mecanismo de delegación principal mediante la delegación secundaria (la clase de implementación es cargada por el cargador de contexto del hilo actual).

Java El cargador de clases de contexto del hilo inicial cuando la aplicación se está ejecutando es el cargador de clases del sistema ( AppClassLoader), y el cargador de clases de contexto del hilo se puede obtener y configurar a través  Thread.currentThread().getContextClassLoader() del  Thread.currentThread().setContextClassLoader() método. Si no se especifica un hilo, heredará su padre. Cargador de clases de contexto de subprocesos de forma predeterminada.

2. Carga dinámica

Java Las clases relevantes se cargarán  al  inicio y URLClassLoader la carga dinámica de clases durante la ejecución del programa se puede realizar pasando.

URLClassLoader Heredado de  ClassLoader, se puede cargar cargando  URL clases desde archivos de recursos, como  URLClassLoader cargando  JAR el paquete de controladores en el siguiente ejemplo.

 
 

Java

copiar código

public static ClassLoader getClassLoader(String driverPath) { // 获取当前线程上下文加载器 ClassLoader parent = Thread.currentThread().getContextClassLoader(); URL[] urls; try { File driver = new File(driverPath); // 驱动包不存在抛出异常 if (!driver.exists()) { throw new FileNotFoundException(); } // File 转 URL 资源格式 list.add(driver.toURI().toURL()); urls = list.toArray(new URL[0]); } catch (Exception e) { throw new RuntimeException(e); } return new URLClassLoader(urls, parent); }

3. Descarga de clases

Aunque  URLClassLoader se puede realizar la carga dinámica de clases,  Java no existe una forma explícita de realizar la descarga de clases, y la descarga de una clase cargada solo se puede lograr mediante la recolección de basura.

Si una clase quiere ser descargada mediante recolección de basura, debe cumplir las tres condiciones siguientes:

  • No hay instancias de la clase actual, es decir, todas las instancias del objeto han sido destruidas.
  • La referencia ( Reference) no existe para la clase actual.
  • La carga de clases para la clase actual ha sido recolectada como basura ( GC).

Siete, aislamiento de clase.

En lo anterior, introdujimos el método de carga de clases y nos centramos en el análisis del mecanismo de delegación principal. A continuación, presentaremos los escenarios de aplicación de la carga de clases.

1. Introducción básica

De manera similar al cargador de contexto de subprocesos mencionado anteriormente (el subproceso secundario hereda el cargador de subprocesos principal de forma predeterminada), la clase a la que se hace referencia de una clase también será cargada por el cargador de clases de la clase de aplicación, es decir, si se hace referencia a ella, También será  ClassA cargado  ClassB por  ClassB la  ClassA carga de clases que se utiliza para cargar, que es uno de los principios de realización del aislamiento de clases.

Entonces, ¿cuál es el papel del aislamiento de clases? Como se mencionó anteriormente, la carga de clases es transitiva por referencia, y una clase en un módulo solo se cargará una vez. Cuando se carga la clase, se juzga  findLoadedClass() si se carga y, de ser así, se omite la carga, es decir,  JVM cada La clase en la máquina virtual tiene y solo hay una.

2. Introducción de ejemplo

Según la introducción anterior, este método causará muchos problemas cuando el programa necesite depender de múltiples versiones de la misma clase.

module-a Por ejemplo, hay dos módulos y  en el proyecto  module-b , dependen de  dos versiones module-c de  1.0 y  respectivamente 2.0 , pero debido a que   están cargados por el mismo proyecto, al final solo se cargará una versión  module-a ,  module-b no   las   dos versiones.AppClassLoaderJVMmodule-c

imagen.png

De acuerdo con  Maven la primera definición en el primer orden de importación, si  module-a la definición es la primera, la versión se cargará finalmente  module-c .  1.0 En este momento, si  module-b se hace referencia  a la nueva característica, 2.0 se lanzará en tiempo de ejecución  ClassNotFoundException.

Java La misma clase cargada por diferentes cargadores de clases  JVM son dos clases diferentes, por lo que para la situación anterior, solo necesitamos personalizar la carga de clases y  cargarlas   a través de dos clases diferentes (aquí carga de clases diferente y No se refiere a categorías de carga de clases diferentes module-a ,  module-bsolo necesita ser cargado por dos objetos de instancia diferentes). Como se mencionó anteriormente, hemos personalizado la implementación de la carga de clases  .  Cargamos el  principio de herencia de referencia de carga de clases   a través  CustomClassLoader de sus dos instancias  loader1 y  loader2 respectivamente  .module-amodule-bmodule-c

imagen.png

Cabe señalar que aquí  CustomClassLoader1 y  CustomClassLoader2 el cargador de clases personalizado correspondiente debe reescribir  loadClass() el método y destruir su mecanismo de delegación principal; de lo contrario, seguirá siendo proxy capa por capa y el cargador que finalmente carga la clase será el cargador de clases del sistema.

Supongo que te gusta

Origin blog.csdn.net/BASK2312/article/details/132318936
Recomendado
Clasificación